完成多出修改
修复拾取界面点击无效问题 修复宠物训练界面不显示训练点问题 新增天赋分享到聊天界面 修复飞行界面无法关闭问题 修复术士宠物的显示问题 为天赋界面添加默认数据库支持 框架现在也可自主选择是否启用 玩家框架和目标框架可取消显示3D头像以及透明度修改 背包和银行也添加透明度自定义支持 优化tooltip性能和背包物品显示方式 修复布局模式在ui缩放后不能正常定位的问题 添加硬核模式危险和死亡的工会通报 添加拾取和已拾取的框体 等等
This commit is contained in:
@@ -21,6 +21,7 @@ local DEFAULTS = {
|
||||
buttonGap = 2,
|
||||
smallBarSize = 27,
|
||||
scale = 1.0,
|
||||
alpha = 1.0,
|
||||
barCount = 3,
|
||||
showHotkey = true,
|
||||
showMacroName = false,
|
||||
@@ -442,7 +443,12 @@ function AB:CreateBars()
|
||||
local anchor = CreateFrame("Frame", "SFramesActionBarAnchor", UIParent)
|
||||
anchor:SetWidth(rowWidth)
|
||||
anchor:SetHeight(size * 3 + gap * 2)
|
||||
anchor:SetPoint("BOTTOM", UIParent, "BOTTOM", db.bottomOffsetX, db.bottomOffsetY)
|
||||
local abPos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["ActionBarBottom"]
|
||||
if abPos and abPos.point and abPos.relativePoint then
|
||||
anchor:SetPoint(abPos.point, UIParent, abPos.relativePoint, abPos.xOfs or 0, abPos.yOfs or 0)
|
||||
else
|
||||
anchor:SetPoint("BOTTOM", UIParent, "BOTTOM", db.bottomOffsetX, db.bottomOffsetY)
|
||||
end
|
||||
anchor:SetScale(db.scale)
|
||||
self.anchor = anchor
|
||||
|
||||
@@ -582,7 +588,12 @@ function AB:CreateBars()
|
||||
local rightHolder = CreateFrame("Frame", "SFramesRightBarHolder", UIParent)
|
||||
rightHolder:SetWidth(size * 2 + gap)
|
||||
rightHolder:SetHeight((size + gap) * BUTTONS_PER_ROW - gap)
|
||||
rightHolder:SetPoint("RIGHT", UIParent, "RIGHT", db.rightOffsetX, db.rightOffsetY)
|
||||
local rbPos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["ActionBarRight"]
|
||||
if rbPos and rbPos.point and rbPos.relativePoint then
|
||||
rightHolder:SetPoint(rbPos.point, UIParent, rbPos.relativePoint, rbPos.xOfs or 0, rbPos.yOfs or 0)
|
||||
else
|
||||
rightHolder:SetPoint("RIGHT", UIParent, "RIGHT", db.rightOffsetX, db.rightOffsetY)
|
||||
end
|
||||
rightHolder:SetScale(db.scale)
|
||||
self.rightHolder = rightHolder
|
||||
|
||||
@@ -761,6 +772,15 @@ function AB:ApplyConfig()
|
||||
end
|
||||
end
|
||||
|
||||
-- Alpha
|
||||
local alpha = db.alpha or 1
|
||||
if alpha < 0.1 then alpha = 0.1 end
|
||||
if alpha > 1 then alpha = 1 end
|
||||
if self.anchor then self.anchor:SetAlpha(alpha) end
|
||||
if self.rightHolder then self.rightHolder:SetAlpha(alpha) end
|
||||
if self.stanceHolder then self.stanceHolder:SetAlpha(alpha) end
|
||||
if self.petHolder then self.petHolder:SetAlpha(alpha) end
|
||||
|
||||
-- Hotkey / macro name(使用缓存表,避免每次 ApplyConfig 都分配临时表)
|
||||
if not self.allButtonsCache then
|
||||
self.allButtonsCache = {}
|
||||
@@ -1000,11 +1020,23 @@ function AB:ApplyGryphon()
|
||||
local ox = db.gryphonOffsetX or 30
|
||||
local oy = db.gryphonOffsetY or 0
|
||||
|
||||
local positions = SFramesDB and SFramesDB.Positions
|
||||
|
||||
self.gryphonLeft:ClearAllPoints()
|
||||
self.gryphonLeft:SetPoint("BOTTOMRIGHT", self.anchor, "BOTTOMLEFT", ox, oy)
|
||||
local posL = positions and positions["GryphonLeft"]
|
||||
if posL and posL.point and posL.relativePoint then
|
||||
self.gryphonLeft:SetPoint(posL.point, UIParent, posL.relativePoint, posL.xOfs or 0, posL.yOfs or 0)
|
||||
else
|
||||
self.gryphonLeft:SetPoint("BOTTOMRIGHT", self.anchor, "BOTTOMLEFT", ox, oy)
|
||||
end
|
||||
|
||||
self.gryphonRight:ClearAllPoints()
|
||||
self.gryphonRight:SetPoint("BOTTOMLEFT", self.anchor, "BOTTOMRIGHT", -ox, oy)
|
||||
local posR = positions and positions["GryphonRight"]
|
||||
if posR and posR.point and posR.relativePoint then
|
||||
self.gryphonRight:SetPoint(posR.point, UIParent, posR.relativePoint, posR.xOfs or 0, posR.yOfs or 0)
|
||||
else
|
||||
self.gryphonRight:SetPoint("BOTTOMLEFT", self.anchor, "BOTTOMRIGHT", -ox, oy)
|
||||
end
|
||||
|
||||
self.gryphonLeft:Show()
|
||||
self.gryphonRight:Show()
|
||||
@@ -1015,13 +1047,25 @@ end
|
||||
--------------------------------------------------------------------------------
|
||||
function AB:ApplyPosition()
|
||||
local db = self:GetDB()
|
||||
local positions = SFramesDB and SFramesDB.Positions
|
||||
|
||||
if self.anchor then
|
||||
self.anchor:ClearAllPoints()
|
||||
self.anchor:SetPoint("BOTTOM", UIParent, "BOTTOM", db.bottomOffsetX, db.bottomOffsetY)
|
||||
local pos = positions and positions["ActionBarBottom"]
|
||||
if pos and pos.point and pos.relativePoint then
|
||||
self.anchor:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||||
else
|
||||
self.anchor:SetPoint("BOTTOM", UIParent, "BOTTOM", db.bottomOffsetX, db.bottomOffsetY)
|
||||
end
|
||||
end
|
||||
if self.rightHolder then
|
||||
self.rightHolder:ClearAllPoints()
|
||||
self.rightHolder:SetPoint("RIGHT", UIParent, "RIGHT", db.rightOffsetX, db.rightOffsetY)
|
||||
local pos = positions and positions["ActionBarRight"]
|
||||
if pos and pos.point and pos.relativePoint then
|
||||
self.rightHolder:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||||
else
|
||||
self.rightHolder:SetPoint("RIGHT", UIParent, "RIGHT", db.rightOffsetX, db.rightOffsetY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1435,6 +1479,35 @@ function AB:Initialize()
|
||||
SFrames:RegisterEvent("ZONE_CHANGED", FixRowAnchors)
|
||||
SFrames:RegisterEvent("ZONE_CHANGED_NEW_AREA", FixRowAnchors)
|
||||
SFrames:RegisterEvent("ZONE_CHANGED_INDOORS", FixRowAnchors)
|
||||
|
||||
-- Register movers
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover then
|
||||
if self.anchor then
|
||||
SFrames.Movers:RegisterMover("ActionBarBottom", self.anchor, "底部动作条",
|
||||
"BOTTOM", "UIParent", "BOTTOM", db.bottomOffsetX, db.bottomOffsetY,
|
||||
function() AB:ApplyStanceBar(); AB:ApplyPetBar(); AB:ApplyGryphon() end)
|
||||
end
|
||||
if self.rightHolder then
|
||||
SFrames.Movers:RegisterMover("ActionBarRight", self.rightHolder, "右侧动作条",
|
||||
"RIGHT", "UIParent", "RIGHT", db.rightOffsetX, db.rightOffsetY)
|
||||
end
|
||||
if self.stanceHolder then
|
||||
SFrames.Movers:RegisterMover("StanceBar", self.stanceHolder, "姿态条",
|
||||
"BOTTOMLEFT", "SFramesActionBarAnchor", "TOPLEFT", 0, db.buttonGap)
|
||||
end
|
||||
if self.petHolder then
|
||||
SFrames.Movers:RegisterMover("PetBar", self.petHolder, "宠物条",
|
||||
"BOTTOMLEFT", "SFramesActionBarAnchor", "TOPLEFT", 0, db.buttonGap)
|
||||
end
|
||||
if self.gryphonLeft then
|
||||
SFrames.Movers:RegisterMover("GryphonLeft", self.gryphonLeft, "狮鹫(左)",
|
||||
"BOTTOMRIGHT", "SFramesActionBarAnchor", "BOTTOMLEFT", db.gryphonOffsetX or 30, db.gryphonOffsetY or 0)
|
||||
end
|
||||
if self.gryphonRight then
|
||||
SFrames.Movers:RegisterMover("GryphonRight", self.gryphonRight, "狮鹫(右)",
|
||||
"BOTTOMLEFT", "SFramesActionBarAnchor", "BOTTOMRIGHT", -(db.gryphonOffsetX or 30), db.gryphonOffsetY or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1621,6 +1621,8 @@ function SFrames.Bags.Bank:Initialize()
|
||||
bankShadow:SetBackdropBorderColor(0, 0, 0, 0.4)
|
||||
local scale = (SFramesDB and SFramesDB.Bags and type(SFramesDB.Bags.bankScale) == "number" and SFramesDB.Bags.bankScale) or 0.85
|
||||
SFBankFrame:SetScale(scale)
|
||||
local bankAlpha = (SFramesDB and SFramesDB.Bags and type(SFramesDB.Bags.bankAlpha) == "number" and SFramesDB.Bags.bankAlpha) or 1
|
||||
SFBankFrame:SetAlpha(bankAlpha)
|
||||
|
||||
local bankTitleIco = SFrames:CreateIcon(SFBankFrame, "gold", 14)
|
||||
bankTitleIco:SetDrawLayer("OVERLAY")
|
||||
|
||||
@@ -578,8 +578,7 @@ local function ApplyBagFramePosition()
|
||||
if pos and pos.point and pos.relPoint and type(pos.x) == "number" and type(pos.y) == "number" then
|
||||
BagFrame:SetPoint(pos.point, UIParent, pos.relPoint, pos.x, pos.y)
|
||||
else
|
||||
-- Default to left side; bank frame defaults to right side.
|
||||
BagFrame:SetPoint("CENTER", UIParent, "CENTER", -360, 0)
|
||||
BagFrame:SetPoint("RIGHT", UIParent, "RIGHT", -20, 0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -942,6 +941,8 @@ function SFrames.Bags.Container:Initialize()
|
||||
bagShadow:SetBackdropBorderColor(0, 0, 0, 0.4)
|
||||
local scale = (SFramesDB and SFramesDB.Bags and type(SFramesDB.Bags.scale) == "number" and SFramesDB.Bags.scale) or 0.85
|
||||
BagFrame:SetScale(scale)
|
||||
local bagAlpha = (SFramesDB and SFramesDB.Bags and type(SFramesDB.Bags.alpha) == "number" and SFramesDB.Bags.alpha) or 1
|
||||
BagFrame:SetAlpha(bagAlpha)
|
||||
|
||||
local titleIco = SFrames:CreateIcon(BagFrame, "backpack", 14)
|
||||
titleIco:SetDrawLayer("OVERLAY")
|
||||
|
||||
1081
BeastTrainingUI.lua
Normal file
1081
BeastTrainingUI.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -81,6 +81,25 @@ local REP_STANDING = {
|
||||
[5] = "友善", [6] = "尊敬", [7] = "崇敬", [8] = "崇拜",
|
||||
}
|
||||
|
||||
local PET_TAB_INDEX = nil
|
||||
|
||||
local PET_FOOD_MAP = {
|
||||
["Meat"] = "肉类", ["Fish"] = "鱼类", ["Cheese"] = "奶酪",
|
||||
["Bread"] = "面包", ["Fungus"] = "蘑菇", ["Fruit"] = "水果",
|
||||
["Raw Meat"] = "生肉", ["Raw Fish"] = "生鱼",
|
||||
["Cooked Meat"] = "熟肉", ["Cooked Fish"] = "熟鱼",
|
||||
}
|
||||
|
||||
local PET_HAPPINESS = {
|
||||
[1] = { text = "不高兴", color = { 0.9, 0.2, 0.2 } },
|
||||
[2] = { text = "满足", color = { 0.9, 0.75, 0.2 } },
|
||||
[3] = { text = "高兴", color = { 0.2, 0.9, 0.2 } },
|
||||
}
|
||||
|
||||
local BEAST_TRAINING_NAMES = {
|
||||
["Beast Training"] = true, ["训练野兽"] = true,
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- EP Stat Weights per class (Turtle WoW)
|
||||
-- Physical DPS: 1 AP = 1 EP; Caster DPS: 1 SP = 1 EP
|
||||
@@ -716,19 +735,45 @@ end
|
||||
--------------------------------------------------------------------------------
|
||||
-- Main Frame
|
||||
--------------------------------------------------------------------------------
|
||||
local function SaveCharPanelPosition()
|
||||
if not (panel and SFramesDB) then return end
|
||||
if not SFramesDB.charPanel then SFramesDB.charPanel = {} end
|
||||
local point, _, relPoint, x, y = panel:GetPoint()
|
||||
if not point or not relPoint then return end
|
||||
SFramesDB.charPanel.position = {
|
||||
point = point,
|
||||
relPoint = relPoint,
|
||||
x = x or 0,
|
||||
y = y or 0,
|
||||
}
|
||||
end
|
||||
|
||||
local function ApplyCharPanelPosition(f)
|
||||
f:ClearAllPoints()
|
||||
local pos = SFramesDB and SFramesDB.charPanel and SFramesDB.charPanel.position
|
||||
if pos and pos.point and pos.relPoint and type(pos.x) == "number" and type(pos.y) == "number" then
|
||||
f:SetPoint(pos.point, UIParent, pos.relPoint, pos.x, pos.y)
|
||||
else
|
||||
f:SetPoint("LEFT", UIParent, "LEFT", 20, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function CreateMainFrame()
|
||||
if panel then return panel end
|
||||
|
||||
local f = CreateFrame("Frame", "SFramesCharacterPanel", UIParent)
|
||||
f:SetWidth(FRAME_W)
|
||||
f:SetHeight(FRAME_H)
|
||||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||||
ApplyCharPanelPosition(f)
|
||||
f:SetFrameStrata("HIGH")
|
||||
f:EnableMouse(true)
|
||||
f:SetMovable(true)
|
||||
f:RegisterForDrag("LeftButton")
|
||||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||||
f:SetScript("OnDragStop", function()
|
||||
this:StopMovingOrSizing()
|
||||
SaveCharPanelPosition()
|
||||
end)
|
||||
f:SetClampedToScreen(true)
|
||||
SetRoundBackdrop(f, T.bg, T.border)
|
||||
CreateShadow(f, 4)
|
||||
@@ -868,6 +913,12 @@ local function CreateMainFrame()
|
||||
|
||||
MakeSep(f, 6, -HEADER_H, -6, -HEADER_H)
|
||||
|
||||
local _, playerClass = UnitClass("player")
|
||||
if (playerClass == "HUNTER" or playerClass == "WARLOCK") and not PET_TAB_INDEX then
|
||||
table.insert(TAB_NAMES, "宠物")
|
||||
PET_TAB_INDEX = table.getn(TAB_NAMES)
|
||||
end
|
||||
|
||||
-- Tab bar
|
||||
tabs = {}
|
||||
pages = {}
|
||||
@@ -1048,6 +1099,7 @@ function CP:UpdateCurrentTab()
|
||||
elseif tab == 2 then self:UpdateReputation()
|
||||
elseif tab == 3 then self:UpdateSkills()
|
||||
elseif tab == 4 then self:UpdateHonor()
|
||||
elseif PET_TAB_INDEX and tab == PET_TAB_INDEX then self:UpdatePet()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1229,6 +1281,7 @@ function CP:BuildAllPages()
|
||||
self:BuildReputationPage()
|
||||
self:BuildSkillsPage()
|
||||
self:BuildHonorPage()
|
||||
if PET_TAB_INDEX then self:BuildPetPage() end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -2645,7 +2698,13 @@ function CP:ShowSwapPopup(slot)
|
||||
hl:SetAllPoints(row)
|
||||
|
||||
row:SetScript("OnClick", function()
|
||||
UseContainerItem(this.itemBag, this.itemSlot)
|
||||
local targetID = popup.anchorSlot and popup.anchorSlot.slotID
|
||||
if targetID then
|
||||
PickupContainerItem(this.itemBag, this.itemSlot)
|
||||
PickupInventoryItem(targetID)
|
||||
else
|
||||
UseContainerItem(this.itemBag, this.itemSlot)
|
||||
end
|
||||
popup:Hide()
|
||||
CP:ScheduleEquipUpdate()
|
||||
end)
|
||||
@@ -3233,6 +3292,29 @@ do
|
||||
end)
|
||||
end
|
||||
|
||||
if BEAST_TRAINING_NAMES[sn] then
|
||||
local tp = 0
|
||||
if GetPetTrainingPoints then
|
||||
local ok2, total, spent = pcall(GetPetTrainingPoints)
|
||||
if ok2 then tp = (total or 0) - (spent or 0) end
|
||||
end
|
||||
local tpFs = MakeFS(sf, 8, "LEFT", { 0.55, 0.85, 0.4 })
|
||||
tpFs:SetPoint("TOPLEFT", nfs, "BOTTOMLEFT", 0, -1)
|
||||
tpFs:SetText("可用训练点数: " .. tostring(tp))
|
||||
sf:EnableMouse(true)
|
||||
sf.skillName = sn
|
||||
sf:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine(this.skillName or "Beast Training", 1, 0.82, 0)
|
||||
GameTooltip:AddLine("打开训练野兽窗口,教授宠物技能", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("消耗训练点数来教授宠物各种技能", 0.7, 0.7, 0.7)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
sf:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
sf:SetHeight(rowH + 12)
|
||||
y = y - 12
|
||||
end
|
||||
|
||||
table.insert(page.skillRows, { frame = sf })
|
||||
y = y - rowH
|
||||
end
|
||||
@@ -3324,6 +3406,432 @@ function CP:UpdateHonor()
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Tab 5: Pet (Hunter)
|
||||
--------------------------------------------------------------------------------
|
||||
function CP:BuildPetPage()
|
||||
if not PET_TAB_INDEX then return end
|
||||
local page = pages[PET_TAB_INDEX]
|
||||
if not page or page.built then return end
|
||||
page.built = true
|
||||
|
||||
local cw = CONTENT_W
|
||||
local contentH = FRAME_H - (HEADER_H + TAB_BAR_H) - INNER_PAD - 4
|
||||
local pad = 8
|
||||
|
||||
local pc = CreateFrame("Frame", nil, page)
|
||||
pc:SetAllPoints(page)
|
||||
page.petContent = pc
|
||||
|
||||
page.noPetText = MakeFS(page, 12, "CENTER", T.dimText)
|
||||
page.noPetText:SetPoint("CENTER", page, "CENTER", 0, 0)
|
||||
page.noPetText:SetText("当前没有宠物")
|
||||
page.noPetText:Hide()
|
||||
|
||||
-- 3D Model
|
||||
local modelH = 180
|
||||
local modelW = cw - 8
|
||||
local modelBg = CreateFrame("Frame", nil, pc)
|
||||
modelBg:SetWidth(modelW)
|
||||
modelBg:SetHeight(modelH)
|
||||
modelBg:SetPoint("TOP", pc, "TOP", 0, -2)
|
||||
SetRoundBackdrop(modelBg, T.modelBg, T.modelBorder)
|
||||
page.modelBgFrame = modelBg
|
||||
|
||||
local modelFrame = CreateFrame("Frame", nil, pc)
|
||||
modelFrame:SetWidth(modelW - 8)
|
||||
modelFrame:SetHeight(modelH - 8)
|
||||
modelFrame:SetPoint("CENTER", modelBg, "CENTER", 0, 0)
|
||||
modelFrame:SetFrameLevel(pc:GetFrameLevel() + 5)
|
||||
|
||||
local model = CreateFrame("PlayerModel", NextName("PetModel"), modelFrame)
|
||||
model:SetAllPoints(modelFrame)
|
||||
page.model = model
|
||||
page.modelFrame = modelFrame
|
||||
|
||||
model:EnableMouse(true)
|
||||
model:EnableMouseWheel(1)
|
||||
model.rotating = false
|
||||
model.curFacing = 0.4
|
||||
model.curScale = 0.55
|
||||
model.posX = 0
|
||||
model.posY = -0.5
|
||||
|
||||
model:SetScript("OnMouseDown", function()
|
||||
if arg1 == "LeftButton" then
|
||||
this.rotating = true
|
||||
this.startX = GetCursorPosition()
|
||||
this.startFacing = this.curFacing or 0
|
||||
elseif arg1 == "RightButton" then
|
||||
this.panning = true
|
||||
local cx, cy = GetCursorPosition()
|
||||
this.panStartX = cx
|
||||
this.panStartY = cy
|
||||
this.panOriginX = this.posX or 0
|
||||
this.panOriginY = this.posY or 0
|
||||
end
|
||||
end)
|
||||
model:SetScript("OnMouseUp", function()
|
||||
if arg1 == "LeftButton" then this.rotating = false
|
||||
elseif arg1 == "RightButton" then this.panning = false end
|
||||
end)
|
||||
model:SetScript("OnMouseWheel", function()
|
||||
local ns = (this.curScale or 1) + arg1 * 0.1
|
||||
if ns < 0.3 then ns = 0.3 end
|
||||
if ns > 3.0 then ns = 3.0 end
|
||||
this.curScale = ns
|
||||
this:SetModelScale(ns)
|
||||
end)
|
||||
model:SetScript("OnUpdate", function()
|
||||
if this.rotating then
|
||||
local cx = GetCursorPosition()
|
||||
local diff = (cx - (this.startX or cx)) * 0.01
|
||||
this.curFacing = (this.startFacing or 0) + diff
|
||||
this:SetFacing(this.curFacing)
|
||||
elseif this.panning then
|
||||
local cx, cy = GetCursorPosition()
|
||||
local es = this:GetEffectiveScale()
|
||||
if es < 0.01 then es = 1 end
|
||||
local dx = (cx - (this.panStartX or cx)) / (es * 35)
|
||||
local dy = (cy - (this.panStartY or cy)) / (es * 35)
|
||||
this.posX = (this.panOriginX or 0) + dx
|
||||
this.posY = (this.panOriginY or 0) + dy
|
||||
this:SetPosition(this.posY, 0, this.posX)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Name overlay at bottom of model
|
||||
page.petNameText = MakeFS(pc, 12, "LEFT", T.gold)
|
||||
page.petNameText:SetPoint("BOTTOMLEFT", modelBg, "BOTTOMLEFT", 8, 4)
|
||||
page.petFamilyText = MakeFS(pc, 9, "RIGHT", T.dimText)
|
||||
page.petFamilyText:SetPoint("BOTTOMRIGHT", modelBg, "BOTTOMRIGHT", -8, 4)
|
||||
|
||||
-- Scrollable stats area below model
|
||||
local statsTop = -(modelH + 6)
|
||||
local statsH = contentH - modelH - 6
|
||||
local scrollArea = CreateScrollFrame(pc, cw, statsH)
|
||||
scrollArea:SetPoint("TOPLEFT", pc, "TOPLEFT", 0, statsTop)
|
||||
page.scrollArea = scrollArea
|
||||
local child = scrollArea.child
|
||||
|
||||
local sY = -4
|
||||
|
||||
-- Info line: happiness, loyalty, training points
|
||||
page.happyLabel = MakeFS(child, 9, "LEFT", T.labelText)
|
||||
page.happyLabel:SetPoint("TOPLEFT", child, "TOPLEFT", pad, sY)
|
||||
page.happyLabel:SetText("心情:")
|
||||
page.happyValue = MakeFS(child, 9, "LEFT", T.valueText)
|
||||
page.happyValue:SetPoint("LEFT", page.happyLabel, "RIGHT", 2, 0)
|
||||
|
||||
page.loyalLabel = MakeFS(child, 9, "LEFT", T.labelText)
|
||||
page.loyalLabel:SetPoint("LEFT", page.happyValue, "RIGHT", 10, 0)
|
||||
page.loyalLabel:SetText("忠诚度:")
|
||||
page.loyalValue = MakeFS(child, 9, "LEFT", T.valueText)
|
||||
page.loyalValue:SetPoint("LEFT", page.loyalLabel, "RIGHT", 2, 0)
|
||||
|
||||
page.tpLabel = MakeFS(child, 9, "LEFT", T.labelText)
|
||||
page.tpLabel:SetPoint("LEFT", page.loyalValue, "RIGHT", 10, 0)
|
||||
page.tpLabel:SetText("训练点:")
|
||||
page.tpValue = MakeFS(child, 9, "LEFT", { 0.55, 0.85, 0.4 })
|
||||
page.tpValue:SetPoint("LEFT", page.tpLabel, "RIGHT", 2, 0)
|
||||
sY = sY - 16
|
||||
|
||||
-- XP bar
|
||||
page.xpSectionLabel = MakeFS(child, 9, "LEFT", T.sectionTitle)
|
||||
page.xpSectionLabel:SetPoint("TOPLEFT", child, "TOPLEFT", pad, sY)
|
||||
page.xpSectionLabel:SetText("经验值")
|
||||
sY = sY - 12
|
||||
|
||||
local xpBf = CreateFrame("Frame", nil, child)
|
||||
xpBf:SetHeight(8)
|
||||
xpBf:SetPoint("TOPLEFT", child, "TOPLEFT", pad, sY)
|
||||
xpBf:SetPoint("TOPRIGHT", child, "TOPRIGHT", -pad, sY)
|
||||
SetPixelBackdrop(xpBf, T.barBg, { 0.15, 0.15, 0.18, 0.5 })
|
||||
page.xpBarFrame = xpBf
|
||||
|
||||
local xpFill = xpBf:CreateTexture(nil, "ARTWORK")
|
||||
xpFill:SetTexture(SFrames:GetTexture())
|
||||
xpFill:SetVertexColor(0.4, 0.65, 0.85, 0.9)
|
||||
xpFill:SetPoint("TOPLEFT", xpBf, "TOPLEFT", 1, -1)
|
||||
xpFill:SetPoint("BOTTOMLEFT", xpBf, "BOTTOMLEFT", 1, 1)
|
||||
xpFill:SetWidth(1)
|
||||
page.xpBarFill = xpFill
|
||||
|
||||
page.xpBarText = MakeFS(xpBf, 7, "CENTER", { 1, 1, 1 })
|
||||
page.xpBarText:SetPoint("CENTER", xpBf, "CENTER", 0, 0)
|
||||
sY = sY - 14
|
||||
|
||||
-- Stats dual-column section
|
||||
sY = self:CreateStatSection(child, "属性与攻防", sY)
|
||||
|
||||
local leftLabels = { "力量", "敏捷", "耐力", "智力", "精神" }
|
||||
local rightLabels = { "攻击", "强度", "伤害", "防御", "护甲" }
|
||||
page.petStatLeft = {}
|
||||
page.petStatRight = {}
|
||||
|
||||
for idx = 1, 5 do
|
||||
local row1 = {}
|
||||
row1.label = MakeFS(child, 9, "LEFT", T.labelText)
|
||||
row1.label:SetPoint("TOPLEFT", child, "TOPLEFT", 14, sY)
|
||||
row1.label:SetText(leftLabels[idx] .. ":")
|
||||
row1.value = MakeFS(child, 9, "RIGHT", T.valueText)
|
||||
row1.value:SetPoint("TOPLEFT", child, "TOPLEFT", 56, sY)
|
||||
row1.value:SetWidth(80)
|
||||
row1.value:SetJustifyH("RIGHT")
|
||||
table.insert(page.petStatLeft, row1)
|
||||
|
||||
local row2 = {}
|
||||
row2.label = MakeFS(child, 9, "LEFT", T.labelText)
|
||||
row2.label:SetPoint("TOPLEFT", child, "TOPLEFT", 160, sY)
|
||||
row2.label:SetText(rightLabels[idx] .. ":")
|
||||
row2.value = MakeFS(child, 9, "RIGHT", T.valueText)
|
||||
row2.value:SetPoint("TOPRIGHT", child, "TOPRIGHT", -14, sY)
|
||||
row2.value:SetWidth(80)
|
||||
row2.value:SetJustifyH("RIGHT")
|
||||
table.insert(page.petStatRight, row2)
|
||||
|
||||
sY = sY - 14
|
||||
end
|
||||
|
||||
-- Resistances
|
||||
sY = sY - 4
|
||||
sY = self:CreateStatSection(child, "抗性", sY)
|
||||
page.resStats = {}
|
||||
local resSchools = { 2, 3, 4, 5, 6 }
|
||||
local resPerRow = 3
|
||||
local resColW = math.floor((cw - 28) / resPerRow)
|
||||
|
||||
for idx = 1, 5 do
|
||||
local col = math.mod(idx - 1, resPerRow)
|
||||
local rowOff = math.floor((idx - 1) / resPerRow)
|
||||
local rx = 14 + col * resColW
|
||||
local ry = sY - rowOff * 14
|
||||
local row = {}
|
||||
local school = resSchools[idx]
|
||||
local rc = T.resistColors[school] or T.labelText
|
||||
row.label = MakeFS(child, 9, "LEFT", rc)
|
||||
row.label:SetPoint("TOPLEFT", child, "TOPLEFT", rx, ry)
|
||||
row.label:SetText(RESIST_NAMES[school] .. ":")
|
||||
row.value = MakeFS(child, 9, "LEFT", T.valueText)
|
||||
row.value:SetPoint("LEFT", row.label, "RIGHT", 2, 0)
|
||||
row.school = school
|
||||
table.insert(page.resStats, row)
|
||||
end
|
||||
sY = sY - math.ceil(5 / resPerRow) * 14
|
||||
|
||||
-- Food (manually created so we can show/hide for warlock)
|
||||
sY = sY - 4
|
||||
local foodHeader = MakeFS(child, 11, "LEFT", T.sectionTitle)
|
||||
foodHeader:SetPoint("TOPLEFT", child, "TOPLEFT", 8, sY)
|
||||
foodHeader:SetText("喜好食物")
|
||||
local foodSep = child:CreateTexture(nil, "ARTWORK")
|
||||
foodSep:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
foodSep:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
|
||||
foodSep:SetHeight(1)
|
||||
foodSep:SetPoint("TOPLEFT", child, "TOPLEFT", 8, sY - 14)
|
||||
foodSep:SetPoint("TOPRIGHT", child, "TOPRIGHT", -8, sY - 14)
|
||||
sY = sY - 18
|
||||
page.foodHeader = foodHeader
|
||||
page.foodSep = foodSep
|
||||
page.foodText = MakeFS(child, 9, "LEFT", T.valueText)
|
||||
page.foodText:SetPoint("TOPLEFT", child, "TOPLEFT", 14, sY)
|
||||
page.foodText:SetWidth(cw - 28)
|
||||
sY = sY - 16
|
||||
|
||||
page.fullContentH = math.abs(sY) + 8
|
||||
scrollArea:SetContentHeight(page.fullContentH)
|
||||
end
|
||||
|
||||
function CP:UpdatePet()
|
||||
if not PET_TAB_INDEX then return end
|
||||
local page = pages[PET_TAB_INDEX]
|
||||
if not page or not page.built then return end
|
||||
|
||||
local hasPetUI = HasPetUI and HasPetUI()
|
||||
local hasPet = UnitExists("pet") and hasPetUI
|
||||
|
||||
if not hasPet then
|
||||
page.petContent:Hide()
|
||||
page.noPetText:Show()
|
||||
return
|
||||
end
|
||||
|
||||
page.petContent:Show()
|
||||
page.noPetText:Hide()
|
||||
|
||||
-- Detect hunter pet vs warlock demon
|
||||
local _, isHunterPet = HasPetUI()
|
||||
|
||||
-- Set 3D model (only reload when pet identity changes to avoid animation reset)
|
||||
if page.model then
|
||||
local petKey = (UnitName("pet") or "") .. ":" .. (UnitLevel("pet") or 0)
|
||||
if page.model.lastPetKey ~= petKey then
|
||||
page.model.lastPetKey = petKey
|
||||
page.model.curFacing = 0.4
|
||||
page.model.curScale = 0.55
|
||||
page.model.posX = 0
|
||||
page.model.posY = -0.5
|
||||
page.model:SetUnit("pet")
|
||||
page.model:SetFacing(0.4)
|
||||
page.model:SetModelScale(0.55)
|
||||
page.model:SetPosition(-0.5, 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local petName = UnitName("pet") or "未知"
|
||||
local petLevel = UnitLevel("pet") or 0
|
||||
page.petNameText:SetText(petName .. " |cff88bbddLv." .. petLevel .. "|r")
|
||||
|
||||
local family = ""
|
||||
if UnitCreatureFamily then
|
||||
local ok, val = pcall(UnitCreatureFamily, "pet")
|
||||
if ok and val then family = val end
|
||||
end
|
||||
page.petFamilyText:SetText(family)
|
||||
|
||||
-- Hunter-only: happiness, loyalty, training points, XP, food
|
||||
if isHunterPet then
|
||||
page.happyLabel:Show(); page.happyValue:Show()
|
||||
page.loyalLabel:Show(); page.loyalValue:Show()
|
||||
page.tpLabel:Show(); page.tpValue:Show()
|
||||
if page.xpSectionLabel then page.xpSectionLabel:Show() end
|
||||
page.xpBarFrame:Show()
|
||||
if page.foodHeader then page.foodHeader:Show() end
|
||||
if page.foodSep then page.foodSep:Show() end
|
||||
page.foodText:Show()
|
||||
|
||||
local happiness = 0
|
||||
if GetPetHappiness then
|
||||
local ok, val = pcall(GetPetHappiness)
|
||||
if ok and val then happiness = val end
|
||||
end
|
||||
local hData = PET_HAPPINESS[happiness]
|
||||
if hData then
|
||||
page.happyValue:SetText(hData.text)
|
||||
page.happyValue:SetTextColor(hData.color[1], hData.color[2], hData.color[3])
|
||||
else
|
||||
page.happyValue:SetText("--")
|
||||
page.happyValue:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
||||
end
|
||||
|
||||
local loyalty = "--"
|
||||
if GetPetLoyalty then
|
||||
local ok, val = pcall(GetPetLoyalty)
|
||||
if ok and val then loyalty = val end
|
||||
end
|
||||
page.loyalValue:SetText(tostring(loyalty))
|
||||
|
||||
local tp = 0
|
||||
if GetPetTrainingPoints then
|
||||
local ok, total, spent = pcall(GetPetTrainingPoints)
|
||||
if ok then tp = (total or 0) - (spent or 0) end
|
||||
end
|
||||
page.tpValue:SetText(tostring(tp))
|
||||
|
||||
local curXP, maxXP = 0, 1
|
||||
if GetPetExperience then
|
||||
local ok, cx, mx = pcall(GetPetExperience)
|
||||
if ok then curXP = cx or 0; maxXP = mx or 1 end
|
||||
end
|
||||
if maxXP == 0 then maxXP = 1 end
|
||||
local xpPct = curXP / maxXP
|
||||
local bw = page.xpBarFrame:GetWidth() - 2
|
||||
if bw < 1 then bw = 1 end
|
||||
page.xpBarFill:SetWidth(math.max(bw * xpPct, 1))
|
||||
page.xpBarText:SetText(curXP .. " / " .. maxXP)
|
||||
else
|
||||
page.happyLabel:Hide(); page.happyValue:Hide()
|
||||
page.loyalLabel:Hide(); page.loyalValue:Hide()
|
||||
page.tpLabel:Hide(); page.tpValue:Hide()
|
||||
if page.xpSectionLabel then page.xpSectionLabel:Hide() end
|
||||
page.xpBarFrame:Hide()
|
||||
if page.foodHeader then page.foodHeader:Hide() end
|
||||
if page.foodSep then page.foodSep:Hide() end
|
||||
page.foodText:Hide()
|
||||
end
|
||||
|
||||
-- Base stats (UnitStat returns base, effective in vanilla; use first non-nil)
|
||||
local statLabels = { "力量", "敏捷", "耐力", "智力", "精神" }
|
||||
for i, r in ipairs(page.petStatLeft) do
|
||||
local base, eff = UnitStat("pet", i)
|
||||
local val = eff or base or 0
|
||||
r.label:SetText(statLabels[i] .. ":")
|
||||
r.value:SetText(tostring(val))
|
||||
end
|
||||
|
||||
-- Combat stats
|
||||
local mainBase, mainMod = 0, 0
|
||||
if UnitAttack then
|
||||
local ok, b, m = pcall(UnitAttack, "pet")
|
||||
if ok then mainBase = b or 0; mainMod = m or 0 end
|
||||
end
|
||||
local apBase, apPos, apNeg = 0, 0, 0
|
||||
if UnitAttackPower then
|
||||
local ok, b, p, n = pcall(UnitAttackPower, "pet")
|
||||
if ok then apBase = b or 0; apPos = p or 0; apNeg = n or 0 end
|
||||
end
|
||||
local ap = apBase + apPos + apNeg
|
||||
local minDmg, maxDmg = 0, 0
|
||||
if UnitDamage then
|
||||
local ok, d1, d2 = pcall(UnitDamage, "pet")
|
||||
if ok then minDmg = d1 or 0; maxDmg = d2 or 0 end
|
||||
end
|
||||
local defBase, defMod = 0, 0
|
||||
if UnitDefense then
|
||||
local ok, b, m = pcall(UnitDefense, "pet")
|
||||
if ok then defBase = b or 0; defMod = m or 0 end
|
||||
end
|
||||
local armorBase, armorEff = 0, 0
|
||||
if UnitArmor then
|
||||
local ok, b, e = pcall(UnitArmor, "pet")
|
||||
if ok then armorBase = b or 0; armorEff = e or 0 end
|
||||
end
|
||||
|
||||
local combatLabels = { "攻击:", "强度:", "伤害:", "防御:", "护甲:" }
|
||||
local combatVals = {
|
||||
tostring(mainBase + mainMod),
|
||||
tostring(ap),
|
||||
string.format("%d-%d", math.floor(minDmg), math.floor(maxDmg)),
|
||||
tostring(defBase + defMod),
|
||||
tostring(armorEff > 0 and armorEff or armorBase),
|
||||
}
|
||||
for i, r in ipairs(page.petStatRight) do
|
||||
r.label:SetText(combatLabels[i])
|
||||
r.value:SetText(combatVals[i] or "0")
|
||||
end
|
||||
|
||||
for _, r in ipairs(page.resStats) do
|
||||
local base, bonus = 0, 0
|
||||
if UnitResistance then
|
||||
local ok, b, bn = pcall(UnitResistance, "pet", r.school)
|
||||
if ok then base = b or 0; bonus = bn or 0 end
|
||||
end
|
||||
r.label:SetText(RESIST_NAMES[r.school] .. ":")
|
||||
r.value:SetText(tostring(base + bonus))
|
||||
end
|
||||
|
||||
if isHunterPet then
|
||||
local foodStr = ""
|
||||
if GetPetFoodTypes then
|
||||
local ok, r1, r2, r3, r4, r5, r6 = pcall(GetPetFoodTypes)
|
||||
if ok then
|
||||
local foods = {}
|
||||
if r1 and r1 ~= "" then table.insert(foods, PET_FOOD_MAP[r1] or r1) end
|
||||
if r2 and r2 ~= "" then table.insert(foods, PET_FOOD_MAP[r2] or r2) end
|
||||
if r3 and r3 ~= "" then table.insert(foods, PET_FOOD_MAP[r3] or r3) end
|
||||
if r4 and r4 ~= "" then table.insert(foods, PET_FOOD_MAP[r4] or r4) end
|
||||
if r5 and r5 ~= "" then table.insert(foods, PET_FOOD_MAP[r5] or r5) end
|
||||
if r6 and r6 ~= "" then table.insert(foods, PET_FOOD_MAP[r6] or r6) end
|
||||
foodStr = table.concat(foods, "、")
|
||||
end
|
||||
end
|
||||
page.foodText:SetText(foodStr)
|
||||
page.scrollArea:SetContentHeight(page.fullContentH)
|
||||
else
|
||||
page.scrollArea:SetContentHeight(page.fullContentH)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Events
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -3336,6 +3844,8 @@ local cpEvents = {
|
||||
"UNIT_ATTACK", "UNIT_DEFENSE", "UNIT_RESISTANCES",
|
||||
"CHAT_MSG_SKILL", "CHAT_MSG_COMBAT_HONOR_GAIN",
|
||||
"CHARACTER_POINTS_CHANGED", "PLAYER_ENTERING_WORLD",
|
||||
"UNIT_PET", "PET_UI_UPDATE", "PET_BAR_UPDATE",
|
||||
"UNIT_PET_EXPERIENCE", "PET_UI_CLOSE", "UNIT_HAPPINESS",
|
||||
}
|
||||
for _, ev in ipairs(cpEvents) do
|
||||
pcall(function() eventFrame:RegisterEvent(ev) end)
|
||||
@@ -3372,12 +3882,16 @@ ToggleCharacter = function(tab)
|
||||
end
|
||||
return
|
||||
end
|
||||
if tab == "PetPaperDollFrame" then
|
||||
CreateMainFrame()
|
||||
CP:Toggle(PET_TAB_INDEX or 1)
|
||||
return
|
||||
end
|
||||
local tabMap = {
|
||||
["PaperDollFrame"] = 1,
|
||||
["ReputationFrame"] = 2,
|
||||
["SkillFrame"] = 3,
|
||||
["HonorFrame"] = 4,
|
||||
["PetPaperDollFrame"] = 1,
|
||||
}
|
||||
CP:Toggle(tabMap[tab] or 1)
|
||||
end
|
||||
|
||||
49
Chat.lua
49
Chat.lua
@@ -1677,20 +1677,31 @@ local function SkinPopupEditBox(popup)
|
||||
local eb = GetPopupEditBox(popup)
|
||||
if not eb or eb._sfSkinned then return end
|
||||
eb._sfSkinned = true
|
||||
local regions = { eb:GetRegions() }
|
||||
for _, r in ipairs(regions) do
|
||||
if r and r:GetObjectType() == "Texture" then
|
||||
r:SetAlpha(0)
|
||||
|
||||
local name = eb:GetName() or ""
|
||||
if name ~= "" then
|
||||
for _, suffix in ipairs({"Left", "Middle", "Mid", "Right"}) do
|
||||
local tex = _G[name .. suffix]
|
||||
if tex and tex.SetAlpha then tex:SetAlpha(0) end
|
||||
end
|
||||
end
|
||||
eb:SetBackdrop({
|
||||
|
||||
local bg = CreateFrame("Frame", nil, eb)
|
||||
bg:SetPoint("TOPLEFT", eb, "TOPLEFT", -3, 3)
|
||||
bg:SetPoint("BOTTOMRIGHT", eb, "BOTTOMRIGHT", 3, -3)
|
||||
bg:SetFrameLevel(math.max((eb:GetFrameLevel() or 1) - 1, 0))
|
||||
bg:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 12,
|
||||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||||
})
|
||||
eb:SetBackdropColor(0.08, 0.06, 0.1, 0.95)
|
||||
eb:SetBackdropBorderColor(0.5, 0.4, 0.55, 0.8)
|
||||
bg:SetBackdropColor(0.08, 0.06, 0.1, 0.95)
|
||||
bg:SetBackdropBorderColor(0.5, 0.4, 0.55, 0.8)
|
||||
eb._sfBg = bg
|
||||
|
||||
if eb.SetTextInsets then eb:SetTextInsets(6, 6, 2, 2) end
|
||||
if eb.SetTextColor then eb:SetTextColor(1, 1, 1) end
|
||||
end
|
||||
|
||||
local function ResolvePopupFrame(whichKey, dialog)
|
||||
@@ -4496,7 +4507,7 @@ function SFrames.Chat:CreateContainer()
|
||||
titleBtn:SetHeight(20)
|
||||
titleBtn:SetFrameStrata("HIGH")
|
||||
titleBtn:SetFrameLevel(f:GetFrameLevel() + 20)
|
||||
titleBtn:RegisterForClicks("LeftButtonUp")
|
||||
titleBtn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
local titleBtnThrottle = 0
|
||||
titleBtn:SetScript("OnUpdate", function()
|
||||
titleBtnThrottle = titleBtnThrottle + arg1
|
||||
@@ -4506,8 +4517,14 @@ function SFrames.Chat:CreateContainer()
|
||||
this:SetWidth(tw + 28)
|
||||
end)
|
||||
titleBtn:SetScript("OnClick", function()
|
||||
if SFrames and SFrames.ConfigUI then
|
||||
SFrames.ConfigUI:OpenUI()
|
||||
if arg1 == "RightButton" then
|
||||
if SFrames.Movers and SFrames.Movers.ToggleLayoutMode then
|
||||
SFrames.Movers:ToggleLayoutMode()
|
||||
end
|
||||
else
|
||||
if SFrames and SFrames.ConfigUI then
|
||||
SFrames.ConfigUI:OpenUI()
|
||||
end
|
||||
end
|
||||
end)
|
||||
titleBtn:SetScript("OnEnter", function()
|
||||
@@ -4516,7 +4533,8 @@ function SFrames.Chat:CreateContainer()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
|
||||
GameTooltip:ClearLines()
|
||||
GameTooltip:AddLine("Nanami UI 设置", 1, 0.84, 0.94)
|
||||
GameTooltip:AddLine("点击打开主设置面板", 0.85, 0.85, 0.85)
|
||||
GameTooltip:AddLine("左键打开主设置面板", 0.85, 0.85, 0.85)
|
||||
GameTooltip:AddLine("右键开启布局模式", 0.85, 0.85, 0.85)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
titleBtn:SetScript("OnLeave", function()
|
||||
@@ -5296,6 +5314,8 @@ function SFrames.Chat:SwitchActiveChatFrame(tab)
|
||||
if cf then
|
||||
self:EnforceChatWindowLock(cf)
|
||||
if cf == activeChatFrame then
|
||||
if UIFrameFadeRemoveFrame then pcall(UIFrameFadeRemoveFrame, cf) end
|
||||
cf.fadeInfo = nil
|
||||
if cf.SetAlpha then cf:SetAlpha(1) end
|
||||
if cf.EnableMouse then cf:EnableMouse(true) end
|
||||
if cf.SetFrameLevel and self.frame and self.frame.inner then
|
||||
@@ -5306,6 +5326,8 @@ function SFrames.Chat:SwitchActiveChatFrame(tab)
|
||||
self.chatFrameIsCombat = isCombat
|
||||
self:RefreshChatBounds()
|
||||
else
|
||||
if UIFrameFadeRemoveFrame then pcall(UIFrameFadeRemoveFrame, cf) end
|
||||
cf.fadeInfo = nil
|
||||
cf:Hide()
|
||||
if cf.SetAlpha then cf:SetAlpha(0) end
|
||||
if cf.EnableMouse then cf:EnableMouse(false) end
|
||||
@@ -6775,6 +6797,11 @@ function SFrames.Chat:Initialize()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover and self.frame then
|
||||
SFrames.Movers:RegisterMover("ChatFrame", self.frame, "聊天框",
|
||||
"BOTTOMLEFT", "UIParent", "BOTTOMLEFT", 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.Chat:ShowCopyDialog(text)
|
||||
|
||||
950
ConfigUI.lua
950
ConfigUI.lua
File diff suppressed because it is too large
Load Diff
220
Core.lua
220
Core.lua
@@ -2,6 +2,44 @@
|
||||
SFrames = {}
|
||||
DEFAULT_CHAT_FRAME:AddMessage("SF: Loading Core.lua...")
|
||||
|
||||
do
|
||||
local _orig_wipe = wipe
|
||||
if _orig_wipe then
|
||||
wipe = function(t)
|
||||
if t == nil then return end
|
||||
return _orig_wipe(t)
|
||||
end
|
||||
end
|
||||
local _orig_tinsert = tinsert
|
||||
if _orig_tinsert then
|
||||
tinsert = function(t, a2, a3)
|
||||
if type(t) ~= "table" then return end
|
||||
if a3 ~= nil then
|
||||
return _orig_tinsert(t, a2, a3)
|
||||
else
|
||||
return _orig_tinsert(t, a2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local _orig_UIFrameFade = UIFrameFade
|
||||
if _orig_UIFrameFade then
|
||||
UIFrameFade = function(frame, fadeInfo)
|
||||
if not frame or not fadeInfo then return end
|
||||
return _orig_UIFrameFade(frame, fadeInfo)
|
||||
end
|
||||
end
|
||||
|
||||
local origOnUpdate = UIParent and UIParent.GetScript and UIParent:GetScript("OnUpdate")
|
||||
if origOnUpdate then
|
||||
UIParent:SetScript("OnUpdate", function()
|
||||
pcall(origOnUpdate)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
BINDING_HEADER_NANAMI_UI = "Nanami-UI"
|
||||
BINDING_NAME_NANAMI_TOGGLE_NAV = "切换导航地图"
|
||||
|
||||
@@ -118,14 +156,22 @@ function SFrames:DoFullInitialize()
|
||||
|
||||
SFrames.Tooltip = CreateFrame("GameTooltip", "SFramesScanTooltip", nil, "GameTooltipTemplate")
|
||||
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
|
||||
SFrames.Tooltip:SetAlpha(0)
|
||||
SFrames.Tooltip:Hide()
|
||||
|
||||
-- Phase 1: Critical modules (unit frames, action bars) — must load immediately
|
||||
if SFramesDB.enableUnitFrames ~= false then
|
||||
if SFrames.Player and SFrames.Player.Initialize then SFrames.Player:Initialize() end
|
||||
if SFrames.Pet and SFrames.Pet.Initialize then SFrames.Pet:Initialize() end
|
||||
if SFrames.Target and SFrames.Target.Initialize then SFrames.Target:Initialize() end
|
||||
if SFrames.ToT and SFrames.ToT.Initialize then SFrames.ToT:Initialize() end
|
||||
if SFrames.Party and SFrames.Party.Initialize then SFrames.Party:Initialize() end
|
||||
if SFramesDB.enablePlayerFrame ~= false then
|
||||
if SFrames.Player and SFrames.Player.Initialize then SFrames.Player:Initialize() end
|
||||
if SFrames.Pet and SFrames.Pet.Initialize then SFrames.Pet:Initialize() end
|
||||
end
|
||||
if SFramesDB.enableTargetFrame ~= false then
|
||||
if SFrames.Target and SFrames.Target.Initialize then SFrames.Target:Initialize() end
|
||||
if SFrames.ToT and SFrames.ToT.Initialize then SFrames.ToT:Initialize() end
|
||||
end
|
||||
if SFramesDB.enablePartyFrame ~= false then
|
||||
if SFrames.Party and SFrames.Party.Initialize then SFrames.Party:Initialize() end
|
||||
end
|
||||
end
|
||||
if SFrames.FloatingTooltip and SFrames.FloatingTooltip.Initialize then SFrames.FloatingTooltip:Initialize() end
|
||||
|
||||
@@ -150,6 +196,7 @@ function SFrames:DoFullInitialize()
|
||||
{ "MapIcons", function() if SFrames.MapIcons and SFrames.MapIcons.Initialize then SFrames.MapIcons:Initialize() end end },
|
||||
{ "Tweaks", function() if SFrames.Tweaks and SFrames.Tweaks.Initialize then SFrames.Tweaks:Initialize() end end },
|
||||
{ "AFKScreen", function() if SFrames.AFKScreen and SFrames.AFKScreen.Initialize then SFrames.AFKScreen:Initialize() end end },
|
||||
{ "LootDisplay", function() if SFrames.LootDisplay and SFrames.LootDisplay.Initialize then SFrames.LootDisplay:Initialize() end end },
|
||||
}
|
||||
|
||||
local idx = 1
|
||||
@@ -344,7 +391,11 @@ function SFrames:InitSlashCommands()
|
||||
SFrames:Print("/nui afk - toggle AFK screen")
|
||||
SFrames:Print("/nui pin - 地图标记 (clear/share)")
|
||||
SFrames:Print("/nui nav - 切换导航地图")
|
||||
SFrames:Print("/nui mapscan - 扫描所有地图更新迷雾揭示数据")
|
||||
SFrames:Print("/nui mapexport - 导出扫描到的地图数据")
|
||||
SFrames:Print("/nui layout - 布局模式(拖拽调整所有框体位置)")
|
||||
SFrames:Print("/nui bind - 按键绑定模式(悬停按钮+按键)")
|
||||
SFrames:Print("/nui talentdb - 天赋默认数据库管理/导出")
|
||||
elseif cmd == "ui" or cmd == "uiconfig" then
|
||||
if SFrames.ConfigUI and SFrames.ConfigUI.Build then SFrames.ConfigUI:Build("ui") end
|
||||
elseif cmd == "chat" or cmd == "chatconfig" then
|
||||
@@ -390,12 +441,78 @@ function SFrames:InitSlashCommands()
|
||||
else
|
||||
SFrames:Print("WorldMap module unavailable.")
|
||||
end
|
||||
elseif cmd == "mapscan" or cmd == "scanmap" then
|
||||
if SFrames.MapReveal and SFrames.MapReveal.ScanAllMaps then
|
||||
SFrames.MapReveal:ScanAllMaps()
|
||||
else
|
||||
SFrames:Print("MapReveal module unavailable.")
|
||||
end
|
||||
elseif cmd == "mapexport" then
|
||||
if SFrames.MapReveal and SFrames.MapReveal.ExportScannedData then
|
||||
SFrames.MapReveal:ExportScannedData()
|
||||
else
|
||||
SFrames:Print("MapReveal module unavailable.")
|
||||
end
|
||||
elseif cmd == "layout" or cmd == "movers" then
|
||||
if SFrames.Movers and SFrames.Movers.ToggleLayoutMode then
|
||||
SFrames.Movers:ToggleLayoutMode()
|
||||
else
|
||||
SFrames:Print("Layout mode unavailable.")
|
||||
end
|
||||
elseif cmd == "bind" or cmd == "keybind" then
|
||||
if SFrames.ActionBars and SFrames.ActionBars.ToggleKeyBindMode then
|
||||
SFrames.ActionBars:ToggleKeyBindMode()
|
||||
else
|
||||
SFrames:Print("ActionBars module unavailable.")
|
||||
end
|
||||
elseif cmd == "keybinds" or cmd == "kb" then
|
||||
local KBM = SFrames.KeyBindManager
|
||||
if not KBM then
|
||||
SFrames:Print("KeyBindManager module unavailable.")
|
||||
elseif args == "" then
|
||||
if SFrames.ConfigUI and SFrames.ConfigUI.Build then
|
||||
SFrames.ConfigUI:Build("keybinds")
|
||||
end
|
||||
elseif string.find(args, "^save ") then
|
||||
local name = string.gsub(args, "^save ", "")
|
||||
if name ~= "" then KBM:SaveProfile(name) end
|
||||
elseif string.find(args, "^load ") then
|
||||
local name = string.gsub(args, "^load ", "")
|
||||
if name ~= "" then KBM:LoadProfile(name) end
|
||||
elseif string.find(args, "^delete ") then
|
||||
local name = string.gsub(args, "^delete ", "")
|
||||
if name ~= "" then KBM:DeleteProfile(name) end
|
||||
elseif args == "list" then
|
||||
local list = KBM:GetProfileList()
|
||||
if table.getn(list) == 0 then
|
||||
SFrames:Print("没有保存的按键绑定方案")
|
||||
else
|
||||
SFrames:Print("按键绑定方案列表:")
|
||||
for _, name in ipairs(list) do
|
||||
local info = KBM:GetProfileInfo(name)
|
||||
local desc = info and (info.charName .. ", " .. info.count .. " 条") or ""
|
||||
SFrames:Print(" |cffffd100" .. name .. "|r " .. desc)
|
||||
end
|
||||
end
|
||||
elseif args == "export" then
|
||||
KBM:ShowExportDialog()
|
||||
elseif args == "import" then
|
||||
KBM:ShowImportDialog()
|
||||
else
|
||||
SFrames:Print("/nui keybinds - 打开设置面板")
|
||||
SFrames:Print("/nui keybinds save <名称> - 保存当前绑定为方案")
|
||||
SFrames:Print("/nui keybinds load <名称> - 加载方案")
|
||||
SFrames:Print("/nui keybinds delete <名称> - 删除方案")
|
||||
SFrames:Print("/nui keybinds list - 列出所有方案")
|
||||
SFrames:Print("/nui keybinds export - 导出当前绑定")
|
||||
SFrames:Print("/nui keybinds import - 导入绑定")
|
||||
end
|
||||
elseif cmd == "talentdb" or cmd == "tdb" then
|
||||
if SFrames.TalentTree and SFrames.TalentTree.HandleTalentDBCommand then
|
||||
SFrames.TalentTree:HandleTalentDBCommand(args)
|
||||
else
|
||||
SFrames:Print("TalentTree module unavailable.")
|
||||
end
|
||||
elseif cmd == "debugbuffs" or cmd == "db" then
|
||||
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r 当前所有 Buff:")
|
||||
@@ -437,7 +554,7 @@ function SFrames:InitSlashCommands()
|
||||
if SFrames.ConfigUI and SFrames.ConfigUI.Build then SFrames.ConfigUI:Build("ui") end
|
||||
else
|
||||
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r Commands: /nui, /nui ui, /nui bags, /nui chat, /nui unlock, /nui lock, /nui test, /nui partyh, /nui partyv, /nui focushelp, /nui mapreveal, /nui stats, /nui afk, /nui pin, /nui bind")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r Commands: /nui, /nui ui, /nui bags, /nui chat, /nui layout, /nui unlock, /nui lock, /nui test, /nui partyh, /nui partyv, /nui focushelp, /nui mapreveal, /nui mapscan, /nui stats, /nui afk, /nui pin, /nui bind, /nui keybinds")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -480,41 +597,44 @@ function SFrames:HideBlizzardFrames()
|
||||
if SFramesDB and SFramesDB.enableUnitFrames == false then
|
||||
-- Keep Blizzard unit frames when Nanami frames are disabled
|
||||
else
|
||||
-- Hide Player Frame
|
||||
if PlayerFrame then
|
||||
PlayerFrame:UnregisterAllEvents()
|
||||
PlayerFrame:Hide()
|
||||
PlayerFrame.Show = function() end
|
||||
if not SFramesDB or SFramesDB.enablePlayerFrame ~= false then
|
||||
if PlayerFrame then
|
||||
PlayerFrame:UnregisterAllEvents()
|
||||
PlayerFrame:Hide()
|
||||
PlayerFrame.Show = function() end
|
||||
end
|
||||
if PetFrame then
|
||||
PetFrame:UnregisterAllEvents()
|
||||
PetFrame:Hide()
|
||||
PetFrame.Show = function() end
|
||||
end
|
||||
end
|
||||
|
||||
-- Hide Pet Frame
|
||||
if PetFrame then
|
||||
PetFrame:UnregisterAllEvents()
|
||||
PetFrame:Hide()
|
||||
PetFrame.Show = function() end
|
||||
if not SFramesDB or SFramesDB.enableTargetFrame ~= false then
|
||||
if TargetFrame then
|
||||
TargetFrame:UnregisterAllEvents()
|
||||
TargetFrame:Hide()
|
||||
TargetFrame.Show = function() end
|
||||
end
|
||||
if ComboFrame then
|
||||
ComboFrame:UnregisterAllEvents()
|
||||
ComboFrame:Hide()
|
||||
ComboFrame.Show = function() end
|
||||
ComboFrame.fadeInfo = ComboFrame.fadeInfo or {}
|
||||
if ComboFrame_Update then
|
||||
ComboFrame_Update = function() end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Hide Target Frame
|
||||
if TargetFrame then
|
||||
TargetFrame:UnregisterAllEvents()
|
||||
TargetFrame:Hide()
|
||||
TargetFrame.Show = function() end
|
||||
end
|
||||
|
||||
-- Hide Combo Frame
|
||||
if ComboFrame then
|
||||
ComboFrame:UnregisterAllEvents()
|
||||
ComboFrame:Hide()
|
||||
ComboFrame.Show = function() end
|
||||
end
|
||||
|
||||
-- Hide Party Frames
|
||||
for i = 1, 4 do
|
||||
local pf = _G["PartyMemberFrame"..i]
|
||||
if pf then
|
||||
pf:UnregisterAllEvents()
|
||||
pf:Hide()
|
||||
pf.Show = function() end
|
||||
if not SFramesDB or SFramesDB.enablePartyFrame ~= false then
|
||||
for i = 1, 4 do
|
||||
local pf = _G["PartyMemberFrame"..i]
|
||||
if pf then
|
||||
pf:UnregisterAllEvents()
|
||||
pf:Hide()
|
||||
pf.Show = function() end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -523,12 +643,12 @@ function SFrames:HideBlizzardFrames()
|
||||
if (not SFramesDB) or (SFramesDB.enableRaidFrames ~= false) then
|
||||
|
||||
local function NeuterBlizzardRaidUI()
|
||||
-- Default Classic UI (1.12)
|
||||
if RaidFrame then
|
||||
RaidFrame:UnregisterAllEvents()
|
||||
RaidFrame:SetScript("OnEvent", nil)
|
||||
RaidFrame:SetScript("OnUpdate", nil)
|
||||
end
|
||||
|
||||
-- Prevent Raid groups from updating and showing
|
||||
for i = 1, NUM_RAID_GROUPS or 8 do
|
||||
local rgf = _G["RaidGroupButton"..i]
|
||||
if rgf then
|
||||
@@ -536,11 +656,18 @@ function SFrames:HideBlizzardFrames()
|
||||
end
|
||||
end
|
||||
|
||||
-- Override pullout generation
|
||||
RaidPullout_Update = function() end
|
||||
RaidPullout_OnEvent = function() end
|
||||
RaidPullout_Update = function() return {} end
|
||||
RaidPullout_OnEvent = function() return {} end
|
||||
RaidGroupFrame_OnEvent = function() return {} end
|
||||
RaidGroupFrame_Update = RaidGroupFrame_Update or function() end
|
||||
|
||||
if not RAID_SUBGROUP_LISTS then RAID_SUBGROUP_LISTS = {} end
|
||||
for i = 1, NUM_RAID_GROUPS or 8 do
|
||||
if not RAID_SUBGROUP_LISTS[i] then
|
||||
RAID_SUBGROUP_LISTS[i] = {}
|
||||
end
|
||||
end
|
||||
|
||||
-- Hide individual pullout frames that might already exist
|
||||
for i = 1, 40 do
|
||||
local pf = _G["RaidPullout"..i]
|
||||
if pf then
|
||||
@@ -549,13 +676,7 @@ function SFrames:HideBlizzardFrames()
|
||||
pf.Show = function() end
|
||||
end
|
||||
end
|
||||
|
||||
-- Hide standard GroupFrames
|
||||
if RaidGroupFrame_OnEvent then
|
||||
RaidGroupFrame_OnEvent = function() end
|
||||
end
|
||||
|
||||
-- Hide newer/backported Compact Raid Frames if they exist
|
||||
if CompactRaidFrameManager then
|
||||
CompactRaidFrameManager:UnregisterAllEvents()
|
||||
CompactRaidFrameManager:Hide()
|
||||
@@ -570,7 +691,6 @@ function SFrames:HideBlizzardFrames()
|
||||
|
||||
NeuterBlizzardRaidUI()
|
||||
|
||||
-- Hook ADDON_LOADED to catch Blizzard_RaidUI loaded on demand
|
||||
local raidHook = CreateFrame("Frame")
|
||||
raidHook:RegisterEvent("ADDON_LOADED")
|
||||
raidHook:SetScript("OnEvent", function()
|
||||
|
||||
@@ -647,6 +647,11 @@ function FM:UpdateDestinations()
|
||||
|
||||
table.sort(reachable, function(a, b) return a.cost < b.cost end)
|
||||
|
||||
if table.getn(reachable) == 0 then
|
||||
CloseTaxiMap()
|
||||
return
|
||||
end
|
||||
|
||||
local npcName = UnitName("NPC") or "飞行管理员"
|
||||
FM.titleFS:SetText(npcName .. " - 飞行路线")
|
||||
|
||||
|
||||
24
GameMenu.lua
24
GameMenu.lua
@@ -68,8 +68,6 @@ end
|
||||
--------------------------------------------------------------------------------
|
||||
local MENU_BUTTON_ICONS = {
|
||||
["GameMenuButtonContinue"] = "exit",
|
||||
["GameMenuButtonOptions"] = "settings",
|
||||
["GameMenuButtonSoundOptions"] = "sound",
|
||||
["GameMenuButtonUIOptions"] = "talent",
|
||||
["GameMenuButtonKeybindings"] = "menu",
|
||||
["GameMenuButtonRatings"] = "backpack",
|
||||
@@ -219,8 +217,6 @@ end
|
||||
local BUTTON_ORDER = {
|
||||
"GameMenuButtonContinue",
|
||||
"__NANAMI_SETTINGS__",
|
||||
"GameMenuButtonOptions",
|
||||
"GameMenuButtonSoundOptions",
|
||||
"GameMenuButtonUIOptions",
|
||||
"GameMenuButtonKeybindings",
|
||||
"GameMenuButtonRatings",
|
||||
@@ -229,6 +225,11 @@ local BUTTON_ORDER = {
|
||||
"GameMenuButtonQuit",
|
||||
}
|
||||
|
||||
local HIDDEN_BUTTONS = {
|
||||
"GameMenuButtonOptions",
|
||||
"GameMenuButtonSoundOptions",
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Frame Styling (called once at PLAYER_LOGIN, before first show)
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -268,6 +269,18 @@ local function StyleGameMenuFrame()
|
||||
-- Create settings button
|
||||
local sBt = CreateSettingsButton(GameMenuFrame)
|
||||
|
||||
-- Hide removed buttons (Video / Sound)
|
||||
for _, name in ipairs(HIDDEN_BUTTONS) do
|
||||
local btn = _G[name]
|
||||
if btn then
|
||||
btn:Hide()
|
||||
btn:SetWidth(0.001)
|
||||
btn:SetHeight(0.001)
|
||||
btn:ClearAllPoints()
|
||||
btn:SetPoint("TOPLEFT", GameMenuFrame, "TOPLEFT", 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- Build a lookup of known names for quick check
|
||||
local knownSet = {}
|
||||
for _, name in ipairs(BUTTON_ORDER) do
|
||||
@@ -275,6 +288,9 @@ local function StyleGameMenuFrame()
|
||||
knownSet[name] = true
|
||||
end
|
||||
end
|
||||
for _, name in ipairs(HIDDEN_BUTTONS) do
|
||||
knownSet[name] = true
|
||||
end
|
||||
|
||||
-- Collect all child buttons that are NOT the settings button
|
||||
local children = { GameMenuFrame:GetChildren() }
|
||||
|
||||
633
KeyBindManager.lua
Normal file
633
KeyBindManager.lua
Normal file
@@ -0,0 +1,633 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Nanami-UI: KeyBind Manager
|
||||
-- Profile-based keybinding management (save/load/delete/rename/export/import)
|
||||
-- Covers ALL keybindings, not just action bars.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
SFrames.KeyBindManager = {}
|
||||
|
||||
local KBM = SFrames.KeyBindManager
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Data helpers
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function EnsureDB()
|
||||
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
||||
if not SFramesGlobalDB.KeyBindProfiles then SFramesGlobalDB.KeyBindProfiles = {} end
|
||||
end
|
||||
|
||||
local function GetCharName()
|
||||
return UnitName("player") or "Unknown"
|
||||
end
|
||||
|
||||
local function GetTimestamp()
|
||||
return time and time() or 0
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Collect / Apply bindings
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function KBM:CollectAllBindings()
|
||||
local result = {}
|
||||
local n = GetNumBindings()
|
||||
for i = 1, n do
|
||||
local command, key1, key2 = GetBinding(i)
|
||||
if command and (key1 or key2) then
|
||||
table.insert(result, {
|
||||
command = command,
|
||||
key1 = key1,
|
||||
key2 = key2,
|
||||
})
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function KBM:ClearAllBindings()
|
||||
local n = GetNumBindings()
|
||||
for i = 1, n do
|
||||
local command, key1, key2 = GetBinding(i)
|
||||
if key2 then SetBinding(key2, nil) end
|
||||
if key1 then SetBinding(key1, nil) end
|
||||
end
|
||||
end
|
||||
|
||||
function KBM:ApplyBindings(data)
|
||||
if not data or type(data) ~= "table" then return false end
|
||||
|
||||
self:ClearAllBindings()
|
||||
|
||||
local applied = 0
|
||||
for _, entry in ipairs(data) do
|
||||
if entry.command then
|
||||
if entry.key1 then
|
||||
SetBinding(entry.key1, entry.command)
|
||||
applied = applied + 1
|
||||
end
|
||||
if entry.key2 then
|
||||
SetBinding(entry.key2, entry.command)
|
||||
applied = applied + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SaveBindings(2)
|
||||
|
||||
if SFrames.ActionBars and SFrames.ActionBars.RefreshAllHotkeys then
|
||||
SFrames.ActionBars:RefreshAllHotkeys()
|
||||
end
|
||||
|
||||
return true, applied
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Base64 encode / decode (pure Lua 5.0 compatible)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
local function Base64Encode(src)
|
||||
if not src or src == "" then return "" end
|
||||
local out = {}
|
||||
local len = string.len(src)
|
||||
local i = 1
|
||||
while i <= len do
|
||||
local a = string.byte(src, i)
|
||||
local b = (i + 1 <= len) and string.byte(src, i + 1) or 0
|
||||
local c = (i + 2 <= len) and string.byte(src, i + 2) or 0
|
||||
local remaining = len - i + 1
|
||||
|
||||
local n = a * 65536 + b * 256 + c
|
||||
|
||||
local c1 = math.floor(n / 262144)
|
||||
local c2 = math.floor(math.mod(n, 262144) / 4096)
|
||||
local c3 = math.floor(math.mod(n, 4096) / 64)
|
||||
local c4 = math.mod(n, 64)
|
||||
|
||||
table.insert(out, string.sub(B64, c1 + 1, c1 + 1))
|
||||
table.insert(out, string.sub(B64, c2 + 1, c2 + 1))
|
||||
if remaining >= 2 then
|
||||
table.insert(out, string.sub(B64, c3 + 1, c3 + 1))
|
||||
else
|
||||
table.insert(out, "=")
|
||||
end
|
||||
if remaining >= 3 then
|
||||
table.insert(out, string.sub(B64, c4 + 1, c4 + 1))
|
||||
else
|
||||
table.insert(out, "=")
|
||||
end
|
||||
|
||||
i = i + 3
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
local B64_INV = {}
|
||||
for i = 1, 64 do
|
||||
B64_INV[string.sub(B64, i, i)] = i - 1
|
||||
end
|
||||
|
||||
local function Base64Decode(src)
|
||||
if not src or src == "" then return "" end
|
||||
src = string.gsub(src, "%s+", "")
|
||||
local out = {}
|
||||
local len = string.len(src)
|
||||
local i = 1
|
||||
while i <= len do
|
||||
local v1 = B64_INV[string.sub(src, i, i)] or 0
|
||||
local v2 = B64_INV[string.sub(src, i + 1, i + 1)] or 0
|
||||
local v3 = B64_INV[string.sub(src, i + 2, i + 2)]
|
||||
local v4 = B64_INV[string.sub(src, i + 3, i + 3)]
|
||||
|
||||
local n = v1 * 262144 + v2 * 4096 + (v3 or 0) * 64 + (v4 or 0)
|
||||
|
||||
table.insert(out, string.char(math.floor(n / 65536)))
|
||||
if v3 then
|
||||
table.insert(out, string.char(math.floor(math.mod(n, 65536) / 256)))
|
||||
end
|
||||
if v4 then
|
||||
table.insert(out, string.char(math.mod(n, 256)))
|
||||
end
|
||||
|
||||
i = i + 4
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Serialization (export/import text format, Base64 encoded)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local EXPORT_PREFIX = "!NKB1!"
|
||||
local SEP = "\t"
|
||||
|
||||
local function EncodeRaw(data)
|
||||
local lines = {}
|
||||
for _, entry in ipairs(data) do
|
||||
local line = entry.command
|
||||
if entry.key1 then
|
||||
line = line .. SEP .. entry.key1
|
||||
else
|
||||
line = line .. SEP
|
||||
end
|
||||
if entry.key2 then
|
||||
line = line .. SEP .. entry.key2
|
||||
end
|
||||
table.insert(lines, line)
|
||||
end
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
local function DecodeRaw(raw)
|
||||
local data = {}
|
||||
for line in string.gfind(raw .. "\n", "(.-)\n") do
|
||||
line = string.gsub(line, "\r", "")
|
||||
if line ~= "" and string.sub(line, 1, 1) ~= "#" then
|
||||
local parts = {}
|
||||
for part in string.gfind(line .. SEP, "(.-)" .. SEP) do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
local command = parts[1]
|
||||
if command and command ~= "" then
|
||||
local key1 = parts[2]
|
||||
local key2 = parts[3]
|
||||
if key1 == "" then key1 = nil end
|
||||
if key2 == "" then key2 = nil end
|
||||
if key1 or key2 then
|
||||
table.insert(data, {
|
||||
command = command,
|
||||
key1 = key1,
|
||||
key2 = key2,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
function KBM:SerializeBindings(data)
|
||||
if not data then data = self:CollectAllBindings() end
|
||||
local raw = EncodeRaw(data)
|
||||
return EXPORT_PREFIX .. Base64Encode(raw)
|
||||
end
|
||||
|
||||
function KBM:DeserializeBindings(text)
|
||||
if not text or text == "" then return nil, "文本为空" end
|
||||
text = string.gsub(text, "^%s+", "")
|
||||
text = string.gsub(text, "%s+$", "")
|
||||
|
||||
if string.len(text) == 0 then return nil, "文本为空" end
|
||||
|
||||
local raw
|
||||
if string.sub(text, 1, string.len(EXPORT_PREFIX)) == EXPORT_PREFIX then
|
||||
local encoded = string.sub(text, string.len(EXPORT_PREFIX) + 1)
|
||||
raw = Base64Decode(encoded)
|
||||
if not raw or raw == "" then return nil, "解码失败" end
|
||||
else
|
||||
raw = text
|
||||
end
|
||||
|
||||
local data = DecodeRaw(raw)
|
||||
if table.getn(data) == 0 then return nil, "未找到有效的绑定数据" end
|
||||
return data
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Profile CRUD
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function KBM:SaveProfile(name)
|
||||
if not name or name == "" then return false, "方案名不能为空" end
|
||||
EnsureDB()
|
||||
local data = self:CollectAllBindings()
|
||||
SFramesGlobalDB.KeyBindProfiles[name] = {
|
||||
timestamp = GetTimestamp(),
|
||||
charName = GetCharName(),
|
||||
bindings = data,
|
||||
}
|
||||
SFrames:Print("按键绑定方案已保存: |cffffd100" .. name .. "|r (" .. table.getn(data) .. " 条)")
|
||||
return true
|
||||
end
|
||||
|
||||
function KBM:LoadProfile(name)
|
||||
if not name or name == "" then return false, "方案名不能为空" end
|
||||
EnsureDB()
|
||||
local profile = SFramesGlobalDB.KeyBindProfiles[name]
|
||||
if not profile then return false, "方案不存在: " .. name end
|
||||
|
||||
local ok, count = self:ApplyBindings(profile.bindings)
|
||||
if ok then
|
||||
SFrames:Print("按键绑定方案已加载: |cffffd100" .. name .. "|r (" .. count .. " 个按键)")
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
function KBM:DeleteProfile(name)
|
||||
if not name or name == "" then return false end
|
||||
EnsureDB()
|
||||
if not SFramesGlobalDB.KeyBindProfiles[name] then return false end
|
||||
SFramesGlobalDB.KeyBindProfiles[name] = nil
|
||||
SFrames:Print("按键绑定方案已删除: |cffffd100" .. name .. "|r")
|
||||
return true
|
||||
end
|
||||
|
||||
function KBM:RenameProfile(oldName, newName)
|
||||
if not oldName or oldName == "" or not newName or newName == "" then
|
||||
return false, "名称不能为空"
|
||||
end
|
||||
EnsureDB()
|
||||
local profiles = SFramesGlobalDB.KeyBindProfiles
|
||||
if not profiles[oldName] then return false, "方案不存在" end
|
||||
if profiles[newName] then return false, "目标名称已存在" end
|
||||
|
||||
profiles[newName] = profiles[oldName]
|
||||
profiles[oldName] = nil
|
||||
SFrames:Print("按键绑定方案已重命名: |cffffd100" .. oldName .. "|r -> |cffffd100" .. newName .. "|r")
|
||||
return true
|
||||
end
|
||||
|
||||
function KBM:GetProfileList()
|
||||
EnsureDB()
|
||||
local list = {}
|
||||
for name, _ in pairs(SFramesGlobalDB.KeyBindProfiles) do
|
||||
table.insert(list, name)
|
||||
end
|
||||
table.sort(list)
|
||||
return list
|
||||
end
|
||||
|
||||
function KBM:GetProfileInfo(name)
|
||||
EnsureDB()
|
||||
local p = SFramesGlobalDB.KeyBindProfiles[name]
|
||||
if not p then return nil end
|
||||
return {
|
||||
name = name,
|
||||
charName = p.charName or "?",
|
||||
timestamp = p.timestamp or 0,
|
||||
count = p.bindings and table.getn(p.bindings) or 0,
|
||||
}
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Export / Import Dialog UI
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local exportFrame, importFrame
|
||||
|
||||
local function CreateDialogFrame(name, titleText, width, height)
|
||||
local f = CreateFrame("Frame", name, UIParent)
|
||||
f:SetWidth(width)
|
||||
f:SetHeight(height)
|
||||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 80)
|
||||
f:SetFrameStrata("TOOLTIP")
|
||||
f:SetFrameLevel(200)
|
||||
f:SetToplevel(true)
|
||||
f:EnableMouse(true)
|
||||
f:SetMovable(true)
|
||||
f:SetClampedToScreen(true)
|
||||
f:RegisterForDrag("LeftButton")
|
||||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||||
|
||||
if SFrames and SFrames.CreateBackdrop then
|
||||
SFrames:CreateBackdrop(f)
|
||||
else
|
||||
f:SetBackdrop({
|
||||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||||
tile = false, edgeSize = 1,
|
||||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||||
})
|
||||
f:SetBackdropColor(0.08, 0.07, 0.1, 0.96)
|
||||
f:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.9)
|
||||
end
|
||||
|
||||
local font = (SFrames and SFrames.GetFont) and SFrames:GetFont() or "Fonts\\ARIALN.TTF"
|
||||
|
||||
local title = f:CreateFontString(nil, "OVERLAY")
|
||||
title:SetFont(font, 13, "OUTLINE")
|
||||
title:SetPoint("TOP", f, "TOP", 0, -10)
|
||||
title:SetText(titleText)
|
||||
local _T = SFrames.ActiveTheme
|
||||
if _T and _T.title then
|
||||
title:SetTextColor(_T.title[1], _T.title[2], _T.title[3])
|
||||
else
|
||||
title:SetTextColor(1, 0.82, 0)
|
||||
end
|
||||
f.title = title
|
||||
|
||||
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
|
||||
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -2)
|
||||
close:SetWidth(20)
|
||||
close:SetHeight(20)
|
||||
|
||||
return f, font
|
||||
end
|
||||
|
||||
local function CreateThemedButton(parent, text, x, y, width, height, onClick)
|
||||
local name = "SFramesKBM_Btn_" .. string.gsub(text, "%s", "") .. "_" .. tostring(math.random(10000, 99999))
|
||||
local btn = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
|
||||
btn:SetWidth(width)
|
||||
btn:SetHeight(height)
|
||||
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||||
btn:SetText(text)
|
||||
btn:SetScript("OnClick", onClick)
|
||||
|
||||
local font = (SFrames and SFrames.GetFont) and SFrames:GetFont() or "Fonts\\ARIALN.TTF"
|
||||
local _T = SFrames.ActiveTheme
|
||||
if not _T then return btn end
|
||||
|
||||
local function HideBtnTex(tex)
|
||||
if not tex then return end
|
||||
if tex.SetTexture then tex:SetTexture(nil) end
|
||||
if tex.SetAlpha then tex:SetAlpha(0) end
|
||||
if tex.Hide then tex:Hide() end
|
||||
end
|
||||
HideBtnTex(btn:GetNormalTexture())
|
||||
HideBtnTex(btn:GetPushedTexture())
|
||||
HideBtnTex(btn:GetHighlightTexture())
|
||||
HideBtnTex(btn:GetDisabledTexture())
|
||||
for _, sfx in ipairs({"Left","Right","Middle"}) do
|
||||
local t = _G[name .. sfx]
|
||||
if t then t:SetAlpha(0); t:Hide() end
|
||||
end
|
||||
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 },
|
||||
})
|
||||
btn:SetBackdropColor(_T.btnBg[1], _T.btnBg[2], _T.btnBg[3], _T.btnBg[4])
|
||||
btn:SetBackdropBorderColor(_T.btnBorder[1], _T.btnBorder[2], _T.btnBorder[3], _T.btnBorder[4])
|
||||
local fs = btn:GetFontString()
|
||||
if fs then
|
||||
fs:SetFont(font, 11, "OUTLINE")
|
||||
fs:SetTextColor(_T.btnText[1], _T.btnText[2], _T.btnText[3])
|
||||
end
|
||||
btn:SetScript("OnEnter", function()
|
||||
this:SetBackdropColor(_T.btnHoverBg[1], _T.btnHoverBg[2], _T.btnHoverBg[3], _T.btnHoverBg[4])
|
||||
this:SetBackdropBorderColor(_T.btnHoverBd[1], _T.btnHoverBd[2], _T.btnHoverBd[3], _T.btnHoverBd[4])
|
||||
local t = this:GetFontString()
|
||||
if t then t:SetTextColor(_T.btnActiveText[1], _T.btnActiveText[2], _T.btnActiveText[3]) end
|
||||
end)
|
||||
btn:SetScript("OnLeave", function()
|
||||
this:SetBackdropColor(_T.btnBg[1], _T.btnBg[2], _T.btnBg[3], _T.btnBg[4])
|
||||
this:SetBackdropBorderColor(_T.btnBorder[1], _T.btnBorder[2], _T.btnBorder[3], _T.btnBorder[4])
|
||||
local t = this:GetFontString()
|
||||
if t then t:SetTextColor(_T.btnText[1], _T.btnText[2], _T.btnText[3]) end
|
||||
end)
|
||||
btn:SetScript("OnMouseDown", function()
|
||||
this:SetBackdropColor(_T.btnDownBg[1], _T.btnDownBg[2], _T.btnDownBg[3], _T.btnDownBg[4])
|
||||
end)
|
||||
btn:SetScript("OnMouseUp", function()
|
||||
this:SetBackdropColor(_T.btnHoverBg[1], _T.btnHoverBg[2], _T.btnHoverBg[3], _T.btnHoverBg[4])
|
||||
end)
|
||||
|
||||
return btn
|
||||
end
|
||||
|
||||
function KBM:ShowExportDialog()
|
||||
local text = self:SerializeBindings()
|
||||
|
||||
if not exportFrame then
|
||||
local f, font = CreateDialogFrame("SFramesKBMExport", "|cffffcc00导出按键绑定|r", 480, 340)
|
||||
|
||||
local desc = f:CreateFontString(nil, "OVERLAY")
|
||||
desc:SetFont(font, 10, "OUTLINE")
|
||||
desc:SetPoint("TOP", f.title, "BOTTOM", 0, -4)
|
||||
desc:SetText("已编码为字符串,Ctrl+A 全选,Ctrl+C 复制")
|
||||
desc:SetTextColor(0.7, 0.7, 0.7)
|
||||
|
||||
local sf = CreateFrame("ScrollFrame", "SFramesKBMExportScroll", f, "UIPanelScrollFrameTemplate")
|
||||
sf:SetPoint("TOPLEFT", f, "TOPLEFT", 12, -48)
|
||||
sf:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -32, 42)
|
||||
|
||||
local edit = CreateFrame("EditBox", "SFramesKBMExportEdit", sf)
|
||||
edit:SetWidth(420)
|
||||
edit:SetHeight(200)
|
||||
edit:SetMultiLine(true)
|
||||
edit:SetFont(font, 10, "OUTLINE")
|
||||
edit:SetAutoFocus(false)
|
||||
edit:SetScript("OnEscapePressed", function() f:Hide() end)
|
||||
sf:SetScrollChild(edit)
|
||||
f.edit = edit
|
||||
|
||||
CreateThemedButton(f, "关闭", 190, -306, 100, 26, function()
|
||||
f:Hide()
|
||||
end)
|
||||
|
||||
exportFrame = f
|
||||
end
|
||||
|
||||
exportFrame.edit:SetText(text)
|
||||
exportFrame:Show()
|
||||
exportFrame.edit:HighlightText()
|
||||
exportFrame.edit:SetFocus()
|
||||
end
|
||||
|
||||
function KBM:ShowImportDialog()
|
||||
if not importFrame then
|
||||
local f, font = CreateDialogFrame("SFramesKBMImport", "|cffffcc00导入按键绑定|r", 480, 370)
|
||||
|
||||
local desc = f:CreateFontString(nil, "OVERLAY")
|
||||
desc:SetFont(font, 10, "OUTLINE")
|
||||
desc:SetPoint("TOP", f.title, "BOTTOM", 0, -4)
|
||||
desc:SetText("将编码后的字符串粘贴到下方 (Ctrl+V),然后点击导入")
|
||||
desc:SetTextColor(0.7, 0.7, 0.7)
|
||||
|
||||
local sf = CreateFrame("ScrollFrame", "SFramesKBMImportScroll", f, "UIPanelScrollFrameTemplate")
|
||||
sf:SetPoint("TOPLEFT", f, "TOPLEFT", 12, -48)
|
||||
sf:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -32, 72)
|
||||
|
||||
local edit = CreateFrame("EditBox", "SFramesKBMImportEdit", sf)
|
||||
edit:SetWidth(420)
|
||||
edit:SetHeight(200)
|
||||
edit:SetMultiLine(true)
|
||||
edit:SetFont(font, 10, "OUTLINE")
|
||||
edit:SetAutoFocus(false)
|
||||
edit:SetScript("OnEscapePressed", function() f:Hide() end)
|
||||
sf:SetScrollChild(edit)
|
||||
f.edit = edit
|
||||
|
||||
local statusLabel = f:CreateFontString(nil, "OVERLAY")
|
||||
statusLabel:SetFont(font, 10, "OUTLINE")
|
||||
statusLabel:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 14, 46)
|
||||
statusLabel:SetWidth(300)
|
||||
statusLabel:SetJustifyH("LEFT")
|
||||
statusLabel:SetText("")
|
||||
f.statusLabel = statusLabel
|
||||
|
||||
CreateThemedButton(f, "导入并应用", 120, -336, 120, 26, function()
|
||||
local inputText = f.edit:GetText()
|
||||
if not inputText or inputText == "" then
|
||||
f.statusLabel:SetText("|cffff4444请先粘贴绑定数据|r")
|
||||
return
|
||||
end
|
||||
local data, err = KBM:DeserializeBindings(inputText)
|
||||
if not data then
|
||||
f.statusLabel:SetText("|cffff4444错误: " .. (err or "未知") .. "|r")
|
||||
return
|
||||
end
|
||||
local ok, count = KBM:ApplyBindings(data)
|
||||
if ok then
|
||||
f.statusLabel:SetText("|cff44ff44成功导入 " .. count .. " 个按键绑定|r")
|
||||
SFrames:Print("已从文本导入 " .. count .. " 个按键绑定")
|
||||
else
|
||||
f.statusLabel:SetText("|cffff4444导入失败|r")
|
||||
end
|
||||
end)
|
||||
|
||||
CreateThemedButton(f, "取消", 250, -336, 100, 26, function()
|
||||
f:Hide()
|
||||
end)
|
||||
|
||||
importFrame = f
|
||||
end
|
||||
|
||||
importFrame.edit:SetText("")
|
||||
importFrame.statusLabel:SetText("")
|
||||
importFrame:Show()
|
||||
importFrame.edit:SetFocus()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Input name dialog (for save / rename)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local inputDialog
|
||||
|
||||
local function ShowInputDialog(titleText, defaultText, callback)
|
||||
if not inputDialog then
|
||||
local f, font = CreateDialogFrame("SFramesKBMInput", "", 360, 120)
|
||||
|
||||
local edit = CreateFrame("EditBox", "SFramesKBMInputEdit", f)
|
||||
edit:SetWidth(320)
|
||||
edit:SetHeight(24)
|
||||
edit:SetPoint("TOP", f, "TOP", 0, -40)
|
||||
edit:SetFont(font, 12, "OUTLINE")
|
||||
edit:SetAutoFocus(true)
|
||||
edit:SetMaxLetters(50)
|
||||
edit: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 },
|
||||
})
|
||||
edit:SetBackdropColor(0.1, 0.1, 0.1, 0.9)
|
||||
edit:SetBackdropBorderColor(0.5, 0.5, 0.5, 0.8)
|
||||
edit:SetTextInsets(6, 6, 0, 0)
|
||||
f.edit = edit
|
||||
|
||||
CreateThemedButton(f, "确定", 90, -78, 80, 24, function()
|
||||
local val = f.edit:GetText()
|
||||
if val and val ~= "" and f.callback then
|
||||
f.callback(val)
|
||||
end
|
||||
f:Hide()
|
||||
end)
|
||||
|
||||
CreateThemedButton(f, "取消", 185, -78, 80, 24, function()
|
||||
f:Hide()
|
||||
end)
|
||||
|
||||
edit:SetScript("OnEscapePressed", function() f:Hide() end)
|
||||
edit:SetScript("OnEnterPressed", function()
|
||||
local val = f.edit:GetText()
|
||||
if val and val ~= "" and f.callback then
|
||||
f.callback(val)
|
||||
end
|
||||
f:Hide()
|
||||
end)
|
||||
|
||||
inputDialog = f
|
||||
end
|
||||
|
||||
inputDialog.title:SetText(titleText)
|
||||
inputDialog.edit:SetText(defaultText or "")
|
||||
inputDialog.callback = callback
|
||||
inputDialog:Show()
|
||||
inputDialog.edit:SetFocus()
|
||||
inputDialog.edit:HighlightText()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Confirm dialog
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local confirmDialog
|
||||
|
||||
local function ShowConfirmDialog(message, callback)
|
||||
if not confirmDialog then
|
||||
local f, font = CreateDialogFrame("SFramesKBMConfirm", "", 360, 110)
|
||||
|
||||
local msg = f:CreateFontString(nil, "OVERLAY")
|
||||
msg:SetFont(font, 11, "OUTLINE")
|
||||
msg:SetPoint("TOP", f, "TOP", 0, -34)
|
||||
msg:SetWidth(320)
|
||||
msg:SetJustifyH("CENTER")
|
||||
msg:SetTextColor(0.9, 0.9, 0.9)
|
||||
f.msg = msg
|
||||
|
||||
CreateThemedButton(f, "确定", 90, -70, 80, 24, function()
|
||||
if f.callback then f.callback() end
|
||||
f:Hide()
|
||||
end)
|
||||
|
||||
CreateThemedButton(f, "取消", 185, -70, 80, 24, function()
|
||||
f:Hide()
|
||||
end)
|
||||
|
||||
confirmDialog = f
|
||||
end
|
||||
|
||||
confirmDialog.title:SetText("|cffffcc00确认|r")
|
||||
confirmDialog.msg:SetText(message)
|
||||
confirmDialog.callback = callback
|
||||
confirmDialog:Show()
|
||||
end
|
||||
|
||||
KBM.ShowInputDialog = ShowInputDialog
|
||||
KBM.ShowConfirmDialog = ShowConfirmDialog
|
||||
816
LootDisplay.lua
Normal file
816
LootDisplay.lua
Normal file
@@ -0,0 +1,816 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Nanami-UI: Loot Display (拾取界面 + 已拾取提示)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
SFrames = SFrames or {}
|
||||
SFrames.LootDisplay = SFrames.LootDisplay or {}
|
||||
|
||||
local LD = SFrames.LootDisplay
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Constants
|
||||
--------------------------------------------------------------------------------
|
||||
local ROW_HEIGHT = 32
|
||||
local ROW_WIDTH = 180
|
||||
local ROW_GAP = 2
|
||||
local ICON_SIZE = 26
|
||||
local TITLE_HEIGHT = 22
|
||||
|
||||
local ALERT_WIDTH = 240
|
||||
local ALERT_HEIGHT = 32
|
||||
local ALERT_GAP = 3
|
||||
local ALERT_ICON = 24
|
||||
local ALERT_FADE_DUR = 0.6
|
||||
local ALERT_FLOAT = 22
|
||||
local ALERT_HOLD = 3.5
|
||||
local ALERT_STAGGER = 0.25
|
||||
local MAX_ALERTS = 10
|
||||
|
||||
local QUALITY_COLORS = {
|
||||
[0] = { 0.62, 0.62, 0.62 },
|
||||
[1] = { 0.92, 0.92, 0.88 },
|
||||
[2] = { 0.12, 1.00, 0.00 },
|
||||
[3] = { 0.00, 0.44, 0.87 },
|
||||
[4] = { 0.64, 0.21, 0.93 },
|
||||
[5] = { 1.00, 0.50, 0.00 },
|
||||
[6] = { 0.90, 0.80, 0.50 },
|
||||
}
|
||||
|
||||
-- Shared rounded backdrop template (matches rest of Nanami-UI)
|
||||
local ROUND_BACKDROP = {
|
||||
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 ROUND_BACKDROP_SMALL = {
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 12,
|
||||
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||||
}
|
||||
|
||||
local ROUND_BACKDROP_SHADOW = {
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 16,
|
||||
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
||||
}
|
||||
|
||||
local ICON_BACKDROP = {
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 10,
|
||||
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||||
}
|
||||
|
||||
local lootRows = {}
|
||||
local activeAlerts = {}
|
||||
local alertAnchor = nil
|
||||
local alertPool = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Helpers
|
||||
--------------------------------------------------------------------------------
|
||||
local function T()
|
||||
return SFrames.ActiveTheme or {}
|
||||
end
|
||||
|
||||
local function Font()
|
||||
return (SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
||||
end
|
||||
|
||||
local function GetDB()
|
||||
if not SFramesDB then SFramesDB = {} end
|
||||
if type(SFramesDB.lootDisplay) ~= "table" then
|
||||
SFramesDB.lootDisplay = {
|
||||
enable = true,
|
||||
alertEnable = true,
|
||||
alertFadeDelay = ALERT_HOLD,
|
||||
scale = 1.0,
|
||||
}
|
||||
end
|
||||
return SFramesDB.lootDisplay
|
||||
end
|
||||
|
||||
local function QColor(quality)
|
||||
local q = tonumber(quality) or 1
|
||||
local c = QUALITY_COLORS[q]
|
||||
if c then return c[1], c[2], c[3] end
|
||||
return 0.92, 0.92, 0.88
|
||||
end
|
||||
|
||||
local function ColorHex(r, g, b)
|
||||
return string.format("%02x%02x%02x", r * 255, g * 255, b * 255)
|
||||
end
|
||||
|
||||
local function GetItemTexture(itemIdOrLink)
|
||||
if not itemIdOrLink then return nil end
|
||||
local a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 = GetItemInfo(itemIdOrLink)
|
||||
if a10 and type(a10) == "string" then return a10 end
|
||||
if a9 and type(a9) == "string" and string.find(a9, "Interface") then return a9 end
|
||||
if a8 and type(a8) == "string" and string.find(a8, "Interface") then return a8 end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function ParseItemLink(link)
|
||||
if not link then return nil end
|
||||
local _, _, colorHex, itemId, itemName = string.find(link, "|c(%x+)|Hitem:(%d+).-|h%[(.-)%]|h|r")
|
||||
if not itemName then return nil end
|
||||
local id = tonumber(itemId) or 0
|
||||
local _, _, quality = GetItemInfo(id)
|
||||
return itemName, quality or 1, id
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- ▸ LOOT FRAME (拾取窗口)
|
||||
--------------------------------------------------------------------------------
|
||||
local lootFrame = nil
|
||||
|
||||
local function CreateLootFrame()
|
||||
if lootFrame then return lootFrame end
|
||||
|
||||
local th = T()
|
||||
local bg = th.panelBg or { 0.06, 0.05, 0.09, 0.95 }
|
||||
local bd = th.panelBorder or { 0.30, 0.25, 0.42, 0.90 }
|
||||
local acc = th.accent or { 1, 0.5, 0.8, 0.98 }
|
||||
|
||||
lootFrame = CreateFrame("Frame", "NanamiLootFrame", UIParent)
|
||||
lootFrame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
lootFrame:SetFrameLevel(50)
|
||||
lootFrame:SetWidth(ROW_WIDTH + 14)
|
||||
lootFrame:SetHeight(TITLE_HEIGHT + 3 * (ROW_HEIGHT + ROW_GAP) + 10)
|
||||
lootFrame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 50, -200)
|
||||
lootFrame:SetClampedToScreen(true)
|
||||
lootFrame:SetMovable(true)
|
||||
lootFrame:EnableMouse(false)
|
||||
|
||||
-- Shadow
|
||||
local shadow = CreateFrame("Frame", nil, lootFrame)
|
||||
shadow:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", -5, 5)
|
||||
shadow:SetPoint("BOTTOMRIGHT", lootFrame, "BOTTOMRIGHT", 5, -5)
|
||||
shadow:SetFrameLevel(math.max(lootFrame:GetFrameLevel() - 1, 0))
|
||||
shadow:SetBackdrop(ROUND_BACKDROP_SHADOW)
|
||||
shadow:SetBackdropColor(0, 0, 0, 0.55)
|
||||
shadow:SetBackdropBorderColor(0, 0, 0, 0.40)
|
||||
shadow:EnableMouse(false)
|
||||
|
||||
-- Background blocker: prevents clicks passing through to frames behind the loot window
|
||||
local blocker = CreateFrame("Frame", nil, lootFrame)
|
||||
blocker:SetAllPoints(lootFrame)
|
||||
blocker:SetFrameLevel(lootFrame:GetFrameLevel())
|
||||
blocker:EnableMouse(true)
|
||||
|
||||
-- Main panel (rounded)
|
||||
lootFrame:SetBackdrop(ROUND_BACKDROP)
|
||||
lootFrame:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 0.95)
|
||||
lootFrame:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 0.90)
|
||||
|
||||
-- Top accent line
|
||||
local acLine = lootFrame:CreateTexture(nil, "ARTWORK")
|
||||
acLine:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
acLine:SetHeight(2)
|
||||
acLine:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", 5, -5)
|
||||
acLine:SetPoint("TOPRIGHT", lootFrame, "TOPRIGHT", -5, -5)
|
||||
acLine:SetVertexColor(acc[1], acc[2], acc[3], 0.65)
|
||||
|
||||
-- Title bar: handles dragging the entire loot frame
|
||||
local titleBar = CreateFrame("Frame", nil, lootFrame)
|
||||
titleBar:SetHeight(TITLE_HEIGHT)
|
||||
titleBar:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", 0, 0)
|
||||
titleBar:SetPoint("TOPRIGHT", lootFrame, "TOPRIGHT", 0, 0)
|
||||
titleBar:SetFrameLevel(lootFrame:GetFrameLevel() + 1)
|
||||
titleBar:EnableMouse(true)
|
||||
titleBar:RegisterForDrag("LeftButton")
|
||||
titleBar:SetScript("OnDragStart", function() lootFrame:StartMoving() end)
|
||||
titleBar:SetScript("OnDragStop", function() lootFrame:StopMovingOrSizing() end)
|
||||
|
||||
-- Title text
|
||||
local titleFS = titleBar:CreateFontString(nil, "OVERLAY")
|
||||
titleFS:SetFont(Font(), 11, "OUTLINE")
|
||||
titleFS:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", 10, -8)
|
||||
local tc = th.title or { 1, 0.88, 1 }
|
||||
titleFS:SetTextColor(tc[1], tc[2], tc[3], 0.95)
|
||||
titleFS:SetText("拾取")
|
||||
lootFrame._title = titleFS
|
||||
|
||||
-- Close button
|
||||
local closeBtn = CreateFrame("Button", nil, lootFrame)
|
||||
closeBtn:SetWidth(18)
|
||||
closeBtn:SetHeight(18)
|
||||
closeBtn:SetPoint("TOPRIGHT", lootFrame, "TOPRIGHT", -5, -5)
|
||||
closeBtn:SetFrameLevel(lootFrame:GetFrameLevel() + 5)
|
||||
closeBtn:RegisterForClicks("LeftButtonUp")
|
||||
closeBtn:SetBackdrop(ROUND_BACKDROP_SMALL)
|
||||
local cbg = th.buttonBg or { 0.35, 0.08, 0.12, 0.85 }
|
||||
local cbd = th.buttonBorder or { 0.50, 0.18, 0.22, 0.80 }
|
||||
closeBtn:SetBackdropColor(cbg[1], cbg[2], cbg[3], cbg[4] or 0.85)
|
||||
closeBtn:SetBackdropBorderColor(cbd[1], cbd[2], cbd[3], cbd[4] or 0.80)
|
||||
local closeFS = closeBtn:CreateFontString(nil, "OVERLAY")
|
||||
closeFS:SetFont(Font(), 10, "OUTLINE")
|
||||
closeFS:SetPoint("CENTER", 0, 0)
|
||||
closeFS:SetText("×")
|
||||
closeFS:SetTextColor(0.9, 0.65, 0.65, 1)
|
||||
closeBtn:SetScript("OnClick", function() CloseLoot() end)
|
||||
closeBtn:SetScript("OnEnter", function()
|
||||
this:SetBackdropColor(0.55, 0.12, 0.15, 0.95)
|
||||
this:SetBackdropBorderColor(0.9, 0.30, 0.35, 1)
|
||||
closeFS:SetTextColor(1, 1, 1, 1)
|
||||
end)
|
||||
closeBtn:SetScript("OnLeave", function()
|
||||
this:SetBackdropColor(cbg[1], cbg[2], cbg[3], cbg[4] or 0.85)
|
||||
this:SetBackdropBorderColor(cbd[1], cbd[2], cbd[3], cbd[4] or 0.80)
|
||||
closeFS:SetTextColor(0.9, 0.65, 0.65, 1)
|
||||
end)
|
||||
|
||||
lootFrame:Hide()
|
||||
return lootFrame
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Loot row
|
||||
--------------------------------------------------------------------------------
|
||||
local function CreateLootRow(parent, index)
|
||||
local th = T()
|
||||
local slotBg = th.slotBg or { 0.07, 0.06, 0.10, 0.85 }
|
||||
local slotBd = th.slotBorder or { 0.18, 0.16, 0.28, 0.60 }
|
||||
local hoverBd = th.slotHover or { 0.38, 0.40, 0.90 }
|
||||
local acc = th.accent or { 1, 0.5, 0.8 }
|
||||
|
||||
local row = CreateFrame("Button", "NanamiLootRow" .. index, parent)
|
||||
row:SetWidth(ROW_WIDTH)
|
||||
row:SetHeight(ROW_HEIGHT)
|
||||
row:SetFrameLevel(parent:GetFrameLevel() + 2)
|
||||
row:RegisterForClicks("LeftButtonUp")
|
||||
|
||||
row:SetBackdrop(ROUND_BACKDROP_SMALL)
|
||||
row:SetBackdropColor(slotBg[1], slotBg[2], slotBg[3], slotBg[4] or 0.85)
|
||||
row:SetBackdropBorderColor(slotBd[1], slotBd[2], slotBd[3], slotBd[4] or 0.60)
|
||||
|
||||
-- Left quality accent bar
|
||||
local qBar = row:CreateTexture(nil, "OVERLAY")
|
||||
qBar:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
qBar:SetWidth(2)
|
||||
qBar:SetPoint("TOPLEFT", row, "TOPLEFT", 3, -3)
|
||||
qBar:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 3, 3)
|
||||
qBar:SetVertexColor(1, 1, 1, 0.6)
|
||||
row.qBar = qBar
|
||||
|
||||
-- Icon frame (rounded border)
|
||||
local iconFrame = CreateFrame("Frame", nil, row)
|
||||
iconFrame:SetWidth(ICON_SIZE + 4)
|
||||
iconFrame:SetHeight(ICON_SIZE + 4)
|
||||
iconFrame:SetPoint("LEFT", row, "LEFT", 7, 0)
|
||||
iconFrame:SetFrameLevel(row:GetFrameLevel() + 1)
|
||||
iconFrame:SetBackdrop(ICON_BACKDROP)
|
||||
iconFrame:SetBackdropColor(0, 0, 0, 0.60)
|
||||
iconFrame:SetBackdropBorderColor(slotBd[1], slotBd[2], slotBd[3], 0.70)
|
||||
iconFrame:EnableMouse(false)
|
||||
row.iconFrame = iconFrame
|
||||
|
||||
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
|
||||
icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 2, -2)
|
||||
icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -2, 2)
|
||||
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
row.icon = icon
|
||||
|
||||
-- Name
|
||||
local nameFS = row:CreateFontString(nil, "OVERLAY")
|
||||
nameFS:SetFont(Font(), 10, "OUTLINE")
|
||||
nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 4, 0)
|
||||
nameFS:SetPoint("RIGHT", row, "RIGHT", -6, 0)
|
||||
nameFS:SetJustifyH("LEFT")
|
||||
row.nameFS = nameFS
|
||||
|
||||
-- Count (bottom-right of icon)
|
||||
local countFS = row:CreateFontString(nil, "OVERLAY")
|
||||
countFS:SetFont(Font(), 8, "OUTLINE")
|
||||
countFS:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -2, 2)
|
||||
countFS:SetJustifyH("RIGHT")
|
||||
countFS:SetTextColor(1, 1, 1, 0.95)
|
||||
row.countFS = countFS
|
||||
|
||||
row._slotBg = slotBg
|
||||
row._slotBd = slotBd
|
||||
row._hoverBd = hoverBd
|
||||
row._acc = acc
|
||||
row:EnableMouse(false)
|
||||
|
||||
row:Hide()
|
||||
return row
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Update loot frame
|
||||
--------------------------------------------------------------------------------
|
||||
local function UpdateLootFrame()
|
||||
local db = GetDB()
|
||||
if not db.enable then return end
|
||||
|
||||
local numItems = GetNumLootItems()
|
||||
if not numItems or numItems == 0 then
|
||||
if lootFrame then lootFrame:Hide() end
|
||||
return
|
||||
end
|
||||
|
||||
CreateLootFrame()
|
||||
|
||||
local validSlots = {}
|
||||
for i = 1, numItems do
|
||||
local texture, itemName, quantity, quality = GetLootSlotInfo(i)
|
||||
if texture then
|
||||
table.insert(validSlots, i)
|
||||
end
|
||||
end
|
||||
|
||||
local numValid = table.getn(validSlots)
|
||||
if numValid == 0 then
|
||||
if lootFrame then lootFrame:Hide() end
|
||||
return
|
||||
end
|
||||
|
||||
local totalH = TITLE_HEIGHT + (numValid * (ROW_HEIGHT + ROW_GAP)) + 10
|
||||
lootFrame:SetWidth(ROW_WIDTH + 14)
|
||||
lootFrame:SetHeight(totalH)
|
||||
lootFrame:SetScale(db.scale or 1.0)
|
||||
|
||||
while table.getn(lootRows) < numValid do
|
||||
local idx = table.getn(lootRows) + 1
|
||||
lootRows[idx] = CreateLootRow(lootFrame, idx)
|
||||
end
|
||||
|
||||
for i = 1, table.getn(lootRows) do lootRows[i]:Hide() end
|
||||
|
||||
for displayIdx = 1, numValid do
|
||||
local slotIdx = validSlots[displayIdx]
|
||||
local row = lootRows[displayIdx]
|
||||
if not row then break end
|
||||
|
||||
row:ClearAllPoints()
|
||||
row:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", 7, -(TITLE_HEIGHT + 2 + (displayIdx - 1) * (ROW_HEIGHT + ROW_GAP)))
|
||||
row:SetWidth(ROW_WIDTH)
|
||||
|
||||
local texture, itemName, quantity, quality = GetLootSlotInfo(slotIdx)
|
||||
row.slotIndex = slotIdx
|
||||
|
||||
row.icon:SetTexture(texture or "Interface\\Icons\\INV_Misc_QuestionMark")
|
||||
|
||||
local r, g, b = QColor(quality)
|
||||
row._qualColor = { r, g, b }
|
||||
row.qBar:SetVertexColor(r, g, b, 0.90)
|
||||
row.iconFrame:SetBackdropBorderColor(r, g, b, 0.65)
|
||||
row:SetBackdropBorderColor(r, g, b, 0.30)
|
||||
|
||||
if itemName then
|
||||
row.nameFS:SetText("|cff" .. ColorHex(r, g, b) .. itemName .. "|r")
|
||||
else
|
||||
row.nameFS:SetText("")
|
||||
end
|
||||
|
||||
if quantity and quantity > 1 then
|
||||
row.countFS:SetText(tostring(quantity))
|
||||
else
|
||||
row.countFS:SetText("")
|
||||
end
|
||||
|
||||
row:Show()
|
||||
|
||||
-- Overlay the Blizzard LootButton on top for click handling
|
||||
local maxBtns = LOOTFRAME_NUMITEMS or 4
|
||||
if displayIdx <= maxBtns then
|
||||
local blizzBtn = _G["LootButton" .. displayIdx]
|
||||
if blizzBtn then
|
||||
blizzBtn:SetID(slotIdx)
|
||||
blizzBtn:SetParent(lootFrame)
|
||||
blizzBtn:ClearAllPoints()
|
||||
blizzBtn:SetAllPoints(row)
|
||||
blizzBtn:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
blizzBtn:SetFrameLevel(row:GetFrameLevel() + 10)
|
||||
blizzBtn:SetAlpha(0)
|
||||
blizzBtn:EnableMouse(true)
|
||||
blizzBtn:Show()
|
||||
|
||||
local rowRef = row
|
||||
blizzBtn._nanamiRow = rowRef
|
||||
blizzBtn:SetScript("OnEnter", function()
|
||||
local rw = this._nanamiRow
|
||||
if rw and rw._acc then
|
||||
rw:SetBackdropBorderColor(rw._acc[1], rw._acc[2], rw._acc[3], 0.85)
|
||||
rw:SetBackdropColor(rw._hoverBd[1], rw._hoverBd[2], rw._hoverBd[3], 0.35)
|
||||
end
|
||||
if rw and rw.slotIndex then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||||
if LootSlotIsItem(rw.slotIndex) then
|
||||
GameTooltip:SetLootItem(rw.slotIndex)
|
||||
else
|
||||
local t, n = GetLootSlotInfo(rw.slotIndex)
|
||||
if n then GameTooltip:SetText(n) end
|
||||
end
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end)
|
||||
blizzBtn:SetScript("OnLeave", function()
|
||||
local rw = this._nanamiRow
|
||||
if rw and rw._slotBg then
|
||||
rw:SetBackdropColor(rw._slotBg[1], rw._slotBg[2], rw._slotBg[3], rw._slotBg[4] or 0.85)
|
||||
if rw._qualColor then
|
||||
rw:SetBackdropBorderColor(rw._qualColor[1], rw._qualColor[2], rw._qualColor[3], 0.35)
|
||||
else
|
||||
rw:SetBackdropBorderColor(rw._slotBd[1], rw._slotBd[2], rw._slotBd[3], rw._slotBd[4] or 0.60)
|
||||
end
|
||||
end
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Hide unused Blizzard buttons
|
||||
local maxBtns = LOOTFRAME_NUMITEMS or 4
|
||||
for i = numValid + 1, maxBtns do
|
||||
local blizzBtn = _G["LootButton" .. i]
|
||||
if blizzBtn then blizzBtn:Hide() end
|
||||
end
|
||||
|
||||
-- Position: use saved mover position if exists, otherwise follow cursor
|
||||
if not lootFrame._posApplied then
|
||||
local hasSaved = false
|
||||
if SFrames.Movers and SFrames.Movers.ApplyPosition then
|
||||
hasSaved = SFrames.Movers:ApplyPosition("LootFrame", lootFrame,
|
||||
"TOPLEFT", "UIParent", "TOPLEFT", 50, -200)
|
||||
end
|
||||
if hasSaved then
|
||||
lootFrame._posApplied = true
|
||||
end
|
||||
end
|
||||
if not lootFrame._posApplied then
|
||||
local cx, cy = GetCursorPosition()
|
||||
local uiS = UIParent:GetEffectiveScale()
|
||||
lootFrame:ClearAllPoints()
|
||||
lootFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", cx / uiS - 30, cy / uiS + 16)
|
||||
end
|
||||
|
||||
lootFrame:Show()
|
||||
end
|
||||
|
||||
local function CloseLootFrame()
|
||||
-- Return Blizzard buttons to LootFrame
|
||||
local maxBtns = LOOTFRAME_NUMITEMS or 4
|
||||
for i = 1, maxBtns do
|
||||
local blizzBtn = _G["LootButton" .. i]
|
||||
if blizzBtn and LootFrame then
|
||||
blizzBtn:SetParent(LootFrame)
|
||||
blizzBtn:SetAlpha(1)
|
||||
blizzBtn:Hide()
|
||||
blizzBtn._nanamiRow = nil
|
||||
end
|
||||
end
|
||||
if lootFrame then lootFrame:Hide() end
|
||||
for i = 1, table.getn(lootRows) do lootRows[i]:Hide() end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- ▸ LOOT ALERTS (已拾取提示)
|
||||
--------------------------------------------------------------------------------
|
||||
local function CreateAlertAnchor()
|
||||
if alertAnchor then return alertAnchor end
|
||||
|
||||
alertAnchor = CreateFrame("Frame", "NanamiLootAlertAnchor", UIParent)
|
||||
alertAnchor:SetWidth(ALERT_WIDTH)
|
||||
alertAnchor:SetHeight(ALERT_HEIGHT)
|
||||
alertAnchor:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 40, 340)
|
||||
alertAnchor:EnableMouse(false)
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.ApplyPosition then
|
||||
SFrames.Movers:ApplyPosition("LootAlert", alertAnchor,
|
||||
"BOTTOMLEFT", "UIParent", "BOTTOMLEFT", 40, 340)
|
||||
end
|
||||
return alertAnchor
|
||||
end
|
||||
|
||||
local function CreateAlertFrame()
|
||||
local th = T()
|
||||
local idx = table.getn(alertPool) + 1
|
||||
local bg = th.panelBg or { 0.06, 0.05, 0.09, 0.92 }
|
||||
local bd = th.panelBorder or { 0.30, 0.25, 0.42, 0.80 }
|
||||
|
||||
local f = CreateFrame("Frame", "NanamiLootAlert" .. idx, UIParent)
|
||||
f:SetFrameStrata("HIGH")
|
||||
f:SetFrameLevel(20)
|
||||
f:SetWidth(ALERT_WIDTH)
|
||||
f:SetHeight(ALERT_HEIGHT)
|
||||
|
||||
-- Rounded backdrop matching UI theme
|
||||
f:SetBackdrop(ROUND_BACKDROP_SMALL)
|
||||
f:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 0.92)
|
||||
f:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 0.80)
|
||||
|
||||
-- Left quality accent bar
|
||||
local qBar = f:CreateTexture(nil, "OVERLAY")
|
||||
qBar:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
qBar:SetWidth(2)
|
||||
qBar:SetPoint("TOPLEFT", f, "TOPLEFT", 3, -3)
|
||||
qBar:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 3, 3)
|
||||
qBar:SetVertexColor(1, 1, 1, 0.7)
|
||||
f.qBar = qBar
|
||||
|
||||
-- Icon with rounded border
|
||||
local iconFrame = CreateFrame("Frame", nil, f)
|
||||
iconFrame:SetWidth(ALERT_ICON + 4)
|
||||
iconFrame:SetHeight(ALERT_ICON + 4)
|
||||
iconFrame:SetPoint("LEFT", f, "LEFT", 7, 0)
|
||||
iconFrame:SetFrameLevel(f:GetFrameLevel() + 1)
|
||||
iconFrame:SetBackdrop(ICON_BACKDROP)
|
||||
iconFrame:SetBackdropColor(0, 0, 0, 0.55)
|
||||
iconFrame:SetBackdropBorderColor(0.25, 0.22, 0.30, 0.6)
|
||||
f.iconFrame = iconFrame
|
||||
|
||||
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
|
||||
icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 2, -2)
|
||||
icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -2, 2)
|
||||
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
f.icon = icon
|
||||
|
||||
-- Name
|
||||
local nameFS = f:CreateFontString(nil, "OVERLAY")
|
||||
nameFS:SetFont(Font(), 10, "OUTLINE")
|
||||
nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 5, 0)
|
||||
nameFS:SetPoint("RIGHT", f, "RIGHT", -30, 0)
|
||||
nameFS:SetJustifyH("LEFT")
|
||||
f.nameFS = nameFS
|
||||
|
||||
-- Count (right side)
|
||||
local countFS = f:CreateFontString(nil, "OVERLAY")
|
||||
countFS:SetFont(Font(), 10, "OUTLINE")
|
||||
countFS:SetPoint("RIGHT", f, "RIGHT", -6, 0)
|
||||
countFS:SetJustifyH("RIGHT")
|
||||
local dim = th.dimText or { 0.55, 0.55, 0.60 }
|
||||
countFS:SetTextColor(dim[1], dim[2], dim[3], 0.95)
|
||||
f.countFS = countFS
|
||||
|
||||
f:Hide()
|
||||
table.insert(alertPool, f)
|
||||
return f
|
||||
end
|
||||
|
||||
local function GetAlertFrame()
|
||||
for i = 1, table.getn(alertPool) do
|
||||
if not alertPool[i]._inUse then return alertPool[i] end
|
||||
end
|
||||
return CreateAlertFrame()
|
||||
end
|
||||
|
||||
local function LayoutAlerts()
|
||||
CreateAlertAnchor()
|
||||
for i = 1, table.getn(activeAlerts) do
|
||||
local af = activeAlerts[i]
|
||||
if af._fadeState ~= "fading" then
|
||||
af:ClearAllPoints()
|
||||
af:SetPoint("BOTTOMLEFT", alertAnchor, "BOTTOMLEFT", 0, (i - 1) * (ALERT_HEIGHT + ALERT_GAP))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function RemoveAlert(frame)
|
||||
frame._inUse = false
|
||||
frame:SetAlpha(0)
|
||||
frame:Hide()
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
local newActive = {}
|
||||
for i = 1, table.getn(activeAlerts) do
|
||||
if activeAlerts[i] ~= frame then
|
||||
table.insert(newActive, activeAlerts[i])
|
||||
end
|
||||
end
|
||||
activeAlerts = newActive
|
||||
LayoutAlerts()
|
||||
end
|
||||
|
||||
local function StartAlertFade(frame, delay)
|
||||
frame._fadeState = "waiting"
|
||||
frame._fadeElapsed = 0
|
||||
frame._fadeDelay = delay
|
||||
|
||||
frame:SetScript("OnUpdate", function()
|
||||
this._fadeElapsed = (this._fadeElapsed or 0) + arg1
|
||||
|
||||
if this._fadeState == "waiting" then
|
||||
if this._fadeElapsed >= this._fadeDelay then
|
||||
this._fadeState = "fading"
|
||||
this._fadeElapsed = 0
|
||||
this._baseY = 0
|
||||
for idx = 1, table.getn(activeAlerts) do
|
||||
if activeAlerts[idx] == this then
|
||||
this._baseY = (idx - 1) * (ALERT_HEIGHT + ALERT_GAP)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif this._fadeState == "fading" then
|
||||
local p = this._fadeElapsed / ALERT_FADE_DUR
|
||||
if p >= 1 then
|
||||
RemoveAlert(this)
|
||||
else
|
||||
this:SetAlpha(1 - p)
|
||||
this:ClearAllPoints()
|
||||
this:SetPoint("BOTTOMLEFT", alertAnchor, "BOTTOMLEFT",
|
||||
0, this._baseY + p * ALERT_FLOAT)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function PruneAlerts()
|
||||
while table.getn(activeAlerts) > MAX_ALERTS do
|
||||
RemoveAlert(activeAlerts[1])
|
||||
end
|
||||
end
|
||||
|
||||
local function ShowLootAlert(texture, name, quality, quantity, link)
|
||||
local db = GetDB()
|
||||
if not db.alertEnable then return end
|
||||
|
||||
for i = 1, table.getn(activeAlerts) do
|
||||
local af = activeAlerts[i]
|
||||
if af._itemName == name and af._fadeState == "waiting" then
|
||||
af._quantity = (af._quantity or 1) + (quantity or 1)
|
||||
if af._quantity > 1 then
|
||||
af.countFS:SetText("x" .. af._quantity)
|
||||
end
|
||||
af._fadeElapsed = 0
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
PruneAlerts()
|
||||
CreateAlertAnchor()
|
||||
|
||||
local f = GetAlertFrame()
|
||||
f._inUse = true
|
||||
f._itemName = name
|
||||
f._quantity = quantity or 1
|
||||
f._link = link
|
||||
|
||||
-- Set icon texture
|
||||
local iconTex = texture or "Interface\\Icons\\INV_Misc_QuestionMark"
|
||||
f.icon:SetTexture(iconTex)
|
||||
|
||||
local r, g, b = QColor(quality)
|
||||
f.qBar:SetVertexColor(r, g, b, 0.90)
|
||||
f.iconFrame:SetBackdropBorderColor(r, g, b, 0.60)
|
||||
|
||||
f.nameFS:SetText("|cff" .. ColorHex(r, g, b) .. (name or "???") .. "|r")
|
||||
|
||||
if f._quantity > 1 then
|
||||
f.countFS:SetText("x" .. f._quantity)
|
||||
else
|
||||
f.countFS:SetText("")
|
||||
end
|
||||
|
||||
-- Re-apply theme backdrop colors (pool reuse)
|
||||
local th = T()
|
||||
local bg = th.panelBg or { 0.06, 0.05, 0.09, 0.92 }
|
||||
local bd = th.panelBorder or { 0.30, 0.25, 0.42, 0.80 }
|
||||
f:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 0.92)
|
||||
f:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 0.80)
|
||||
|
||||
f:SetAlpha(1)
|
||||
f:Show()
|
||||
|
||||
table.insert(activeAlerts, f)
|
||||
LayoutAlerts()
|
||||
|
||||
local hold = db.alertFadeDelay or ALERT_HOLD
|
||||
local stagger = table.getn(activeAlerts) * ALERT_STAGGER
|
||||
StartAlertFade(f, hold + stagger)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Parse CHAT_MSG_LOOT
|
||||
--------------------------------------------------------------------------------
|
||||
local function ParseLootMessage(msg)
|
||||
if not msg then return end
|
||||
|
||||
local _, _, link, countStr = string.find(msg, "(|c%x+|Hitem.-%[.-%]|h|r)x(%d+)")
|
||||
if not link then
|
||||
_, _, link = string.find(msg, "(|c%x+|Hitem.-%[.-%]|h|r)")
|
||||
end
|
||||
|
||||
if link then
|
||||
local itemName, quality, itemId = ParseItemLink(link)
|
||||
if itemName then
|
||||
local count = tonumber(countStr) or 1
|
||||
-- Try full link first (most reliable), then itemId, then itemString
|
||||
local tex = GetItemTexture(link)
|
||||
if not tex and itemId and itemId > 0 then
|
||||
tex = GetItemTexture(itemId)
|
||||
end
|
||||
if not tex and itemId and itemId > 0 then
|
||||
tex = GetItemTexture("item:" .. itemId .. ":0:0:0")
|
||||
end
|
||||
ShowLootAlert(tex, itemName, quality, count, link)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Money
|
||||
local isMoney = false
|
||||
if string.find(msg, "铜") or string.find(msg, "银") or string.find(msg, "金")
|
||||
or string.find(msg, "[Cc]opper") or string.find(msg, "[Ss]ilver") or string.find(msg, "[Gg]old") then
|
||||
if string.find(msg, "拾取") or string.find(msg, "获得") or string.find(msg, "loot") or string.find(msg, "receive") then
|
||||
isMoney = true
|
||||
end
|
||||
end
|
||||
|
||||
if isMoney then
|
||||
local cleanMsg = string.gsub(msg, "|c%x+", "")
|
||||
cleanMsg = string.gsub(cleanMsg, "|r", "")
|
||||
local _, _, moneyPart = string.find(cleanMsg, "[::]%s*(.+)$")
|
||||
if not moneyPart then
|
||||
_, _, moneyPart = string.find(cleanMsg, "loot%s+(.+)$")
|
||||
end
|
||||
if not moneyPart then
|
||||
_, _, moneyPart = string.find(cleanMsg, "获得了?%s*(.+)$")
|
||||
end
|
||||
if moneyPart then
|
||||
moneyPart = string.gsub(moneyPart, "[%.。]+$", "")
|
||||
ShowLootAlert("Interface\\Icons\\INV_Misc_Coin_01", moneyPart, 1, 1, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Initialize
|
||||
--------------------------------------------------------------------------------
|
||||
function LD:Initialize()
|
||||
local db = GetDB()
|
||||
if not db.enable then return end
|
||||
|
||||
CreateLootFrame()
|
||||
CreateAlertAnchor()
|
||||
|
||||
-- Apply saved positions so frames have valid coordinates for the Mover system
|
||||
if SFrames.Movers and SFrames.Movers.ApplyPosition then
|
||||
local applied = SFrames.Movers:ApplyPosition("LootFrame", lootFrame,
|
||||
"TOPLEFT", "UIParent", "TOPLEFT", 50, -200)
|
||||
if applied then lootFrame._posApplied = true end
|
||||
|
||||
SFrames.Movers:ApplyPosition("LootAlert", alertAnchor,
|
||||
"BOTTOMLEFT", "UIParent", "BOTTOMLEFT", 40, 340)
|
||||
end
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover then
|
||||
SFrames.Movers:RegisterMover("LootFrame", lootFrame, "拾取窗口",
|
||||
"TOPLEFT", "UIParent", "TOPLEFT", 50, -200)
|
||||
SFrames.Movers:RegisterMover("LootAlert", alertAnchor, "已拾取提示",
|
||||
"BOTTOMLEFT", "UIParent", "BOTTOMLEFT", 40, 340)
|
||||
end
|
||||
|
||||
SFrames:RegisterEvent("LOOT_OPENED", function()
|
||||
if GetDB().enable then UpdateLootFrame() end
|
||||
end)
|
||||
|
||||
SFrames:RegisterEvent("LOOT_SLOT_CLEARED", function()
|
||||
if GetDB().enable and lootFrame and lootFrame:IsShown() then
|
||||
UpdateLootFrame()
|
||||
end
|
||||
end)
|
||||
|
||||
SFrames:RegisterEvent("LOOT_CLOSED", function()
|
||||
CloseLootFrame()
|
||||
end)
|
||||
|
||||
SFrames:RegisterEvent("CHAT_MSG_LOOT", function()
|
||||
local playerName = UnitName("player")
|
||||
if not playerName then return end
|
||||
local msg = arg1 or ""
|
||||
if string.find(msg, playerName) or string.find(msg, "你获得") or string.find(msg, "你拾取") or string.find(msg, "You receive") then
|
||||
ParseLootMessage(msg)
|
||||
end
|
||||
end)
|
||||
|
||||
local function HideBlizzardLoot()
|
||||
if LootFrame then
|
||||
LootFrame:EnableMouse(false)
|
||||
LootFrame:SetAlpha(0)
|
||||
LootFrame:ClearAllPoints()
|
||||
LootFrame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -10000, 10000)
|
||||
local origShow = LootFrame.Show
|
||||
LootFrame.Show = function(self)
|
||||
origShow(self)
|
||||
self:EnableMouse(false)
|
||||
self:SetAlpha(0)
|
||||
self:ClearAllPoints()
|
||||
self:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -10000, 10000)
|
||||
end
|
||||
end
|
||||
end
|
||||
HideBlizzardLoot()
|
||||
|
||||
local lootHook = CreateFrame("Frame")
|
||||
lootHook:RegisterEvent("ADDON_LOADED")
|
||||
lootHook:SetScript("OnEvent", function()
|
||||
if arg1 == "Blizzard_Loot" then HideBlizzardLoot() end
|
||||
end)
|
||||
end
|
||||
225
MapReveal.lua
225
MapReveal.lua
@@ -16,6 +16,7 @@ local errata = {
|
||||
}
|
||||
|
||||
-- Turtle WoW new/modified zones not present in LibMapOverlayData
|
||||
-- Updated for latest Turtle WoW version
|
||||
local TurtleWoW_Zones = {
|
||||
["StonetalonMountains"] = {
|
||||
"SUNROCKRETREAT:512:256:256:256", "WINDSHEARCRAG:256:256:512:256",
|
||||
@@ -65,6 +66,9 @@ local TurtleWoW_Zones = {
|
||||
},
|
||||
}
|
||||
|
||||
-- Runtime-discovered overlay data (populated by ScanAllMaps)
|
||||
local scannedOverlays = {}
|
||||
|
||||
local function IsTurtleWoW()
|
||||
return TargetHPText and TargetHPPercText
|
||||
end
|
||||
@@ -85,6 +89,23 @@ local function PatchOverlayDB()
|
||||
for zone, data in pairs(TurtleWoW_Zones) do
|
||||
db[zone] = data
|
||||
end
|
||||
|
||||
for zone, data in pairs(scannedOverlays) do
|
||||
if not db[zone] then
|
||||
db[zone] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function MergeScannedData()
|
||||
local db = GetOverlayDB()
|
||||
if not db then return end
|
||||
|
||||
for zone, data in pairs(scannedOverlays) do
|
||||
if not db[zone] or table.getn(db[zone]) < table.getn(data) then
|
||||
db[zone] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function GetConfig()
|
||||
@@ -285,3 +306,207 @@ function MapReveal:Refresh()
|
||||
WorldMapFrame_Update()
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Map Scanner: enumerate all continents/zones, collect overlay data from
|
||||
-- explored areas, discover new zones, and merge into the overlay database.
|
||||
-- Usage: /nui mapscan or SFrames.MapReveal:ScanAllMaps()
|
||||
--------------------------------------------------------------------------------
|
||||
local scanFrame = nil
|
||||
local scanQueue = {}
|
||||
local scanIndex = 0
|
||||
local scanRunning = false
|
||||
local scanResults = {}
|
||||
local scanNewZones = {}
|
||||
local scanUpdatedZones = {}
|
||||
local savedMapC, savedMapZ = 0, 0
|
||||
|
||||
local function ExtractOverlayName(fullPath)
|
||||
if not fullPath or fullPath == "" then return nil end
|
||||
local _, _, name = string.find(fullPath, "\\([^\\]+)$")
|
||||
return name and string.upper(name) or nil
|
||||
end
|
||||
|
||||
local function ProcessScanZone()
|
||||
if scanIndex > table.getn(scanQueue) then
|
||||
MapReveal:FinishScan()
|
||||
return
|
||||
end
|
||||
|
||||
local entry = scanQueue[scanIndex]
|
||||
SetMapZoom(entry.cont, entry.zone)
|
||||
|
||||
local mapFile = GetMapInfo and GetMapInfo() or ""
|
||||
if mapFile == "" then
|
||||
scanIndex = scanIndex + 1
|
||||
return
|
||||
end
|
||||
|
||||
local numOverlays = GetNumMapOverlays and GetNumMapOverlays() or 0
|
||||
if numOverlays > 0 then
|
||||
local overlays = {}
|
||||
for i = 1, numOverlays do
|
||||
local texName, texW, texH, offX, offY = GetMapOverlayInfo(i)
|
||||
if texName and texName ~= "" then
|
||||
local name = ExtractOverlayName(texName)
|
||||
if name then
|
||||
table.insert(overlays, name .. ":" .. texW .. ":" .. texH .. ":" .. offX .. ":" .. offY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if table.getn(overlays) > 0 then
|
||||
local db = GetOverlayDB()
|
||||
local existing = db and db[mapFile]
|
||||
local existingCount = existing and table.getn(existing) or 0
|
||||
|
||||
if not existing then
|
||||
scanNewZones[mapFile] = overlays
|
||||
scanResults[mapFile] = { overlays = overlays, status = "new", count = table.getn(overlays) }
|
||||
elseif table.getn(overlays) > existingCount then
|
||||
scanUpdatedZones[mapFile] = overlays
|
||||
scanResults[mapFile] = { overlays = overlays, status = "updated", count = table.getn(overlays), oldCount = existingCount }
|
||||
else
|
||||
scanResults[mapFile] = { status = "ok", count = existingCount }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scanIndex = scanIndex + 1
|
||||
end
|
||||
|
||||
function MapReveal:FinishScan()
|
||||
scanRunning = false
|
||||
if scanFrame then
|
||||
scanFrame:SetScript("OnUpdate", nil)
|
||||
end
|
||||
|
||||
if savedMapZ > 0 then
|
||||
SetMapZoom(savedMapC, savedMapZ)
|
||||
elseif savedMapC > 0 then
|
||||
SetMapZoom(savedMapC, 0)
|
||||
else
|
||||
if SetMapToCurrentZone then SetMapToCurrentZone() end
|
||||
end
|
||||
|
||||
local cf = DEFAULT_CHAT_FRAME
|
||||
local newCount = 0
|
||||
local updCount = 0
|
||||
|
||||
for zone, overlays in pairs(scanNewZones) do
|
||||
newCount = newCount + 1
|
||||
scannedOverlays[zone] = overlays
|
||||
end
|
||||
for zone, overlays in pairs(scanUpdatedZones) do
|
||||
updCount = updCount + 1
|
||||
scannedOverlays[zone] = overlays
|
||||
end
|
||||
|
||||
MergeScannedData()
|
||||
|
||||
cf:AddMessage("|cffffb3d9[Nanami-UI]|r 地图扫描完成!")
|
||||
cf:AddMessage(string.format(" 扫描了 |cff00ff00%d|r 个区域", table.getn(scanQueue)))
|
||||
|
||||
if newCount > 0 then
|
||||
cf:AddMessage(string.format(" 发现 |cff00ff00%d|r 个新区域 (已自动添加迷雾数据):", newCount))
|
||||
for zone, overlays in pairs(scanNewZones) do
|
||||
cf:AddMessage(" |cff00ffff" .. zone .. "|r (" .. table.getn(overlays) .. " 个覆盖层)")
|
||||
end
|
||||
end
|
||||
|
||||
if updCount > 0 then
|
||||
cf:AddMessage(string.format(" 更新了 |cffffff00%d|r 个区域 (发现更多已探索覆盖层):", updCount))
|
||||
for zone, info in pairs(scanResults) do
|
||||
if info.status == "updated" then
|
||||
cf:AddMessage(" |cffffff00" .. zone .. "|r (" .. (info.oldCount or 0) .. " -> " .. info.count .. ")")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if newCount == 0 and updCount == 0 then
|
||||
cf:AddMessage(" 所有区域数据已是最新,未发现变动。")
|
||||
end
|
||||
|
||||
cf:AddMessage(" 提示: 新发现的区域仅记录已探索区域的覆盖层,完全探索后再次扫描可获取完整数据。")
|
||||
|
||||
if WorldMapFrame and WorldMapFrame:IsShown() then
|
||||
WorldMapFrame_Update()
|
||||
end
|
||||
end
|
||||
|
||||
function MapReveal:ScanAllMaps()
|
||||
if scanRunning then
|
||||
SFrames:Print("地图扫描正在进行中...")
|
||||
return
|
||||
end
|
||||
|
||||
local db = GetOverlayDB()
|
||||
if not db then
|
||||
SFrames:Print("MapReveal: 未找到覆盖层数据库,无法扫描。")
|
||||
return
|
||||
end
|
||||
|
||||
scanRunning = true
|
||||
scanQueue = {}
|
||||
scanIndex = 1
|
||||
scanResults = {}
|
||||
scanNewZones = {}
|
||||
scanUpdatedZones = {}
|
||||
|
||||
savedMapC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
|
||||
savedMapZ = GetCurrentMapZone and GetCurrentMapZone() or 0
|
||||
|
||||
local numContinents = 0
|
||||
if GetMapContinents then
|
||||
local continents = { GetMapContinents() }
|
||||
numContinents = table.getn(continents)
|
||||
end
|
||||
|
||||
for c = 1, numContinents do
|
||||
local zones = { GetMapZones(c) }
|
||||
for z = 1, table.getn(zones) do
|
||||
table.insert(scanQueue, { cont = c, zone = z, name = zones[z] or "" })
|
||||
end
|
||||
end
|
||||
|
||||
SFrames:Print("开始扫描所有地图... (共 " .. table.getn(scanQueue) .. " 个区域)")
|
||||
|
||||
if not scanFrame then
|
||||
scanFrame = CreateFrame("Frame")
|
||||
end
|
||||
|
||||
local scanElapsed = 0
|
||||
scanFrame:SetScript("OnUpdate", function()
|
||||
scanElapsed = scanElapsed + (arg1 or 0)
|
||||
if scanElapsed < 0.02 then return end
|
||||
scanElapsed = 0
|
||||
|
||||
if not scanRunning then return end
|
||||
local batch = 0
|
||||
while scanIndex <= table.getn(scanQueue) and batch < 5 do
|
||||
ProcessScanZone()
|
||||
batch = batch + 1
|
||||
end
|
||||
if scanIndex > table.getn(scanQueue) then
|
||||
MapReveal:FinishScan()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function MapReveal:ExportScannedData()
|
||||
local cf = DEFAULT_CHAT_FRAME
|
||||
local hasData = false
|
||||
|
||||
for zone, overlays in pairs(scannedOverlays) do
|
||||
hasData = true
|
||||
cf:AddMessage("|cffffb3d9[MapReveal Export]|r |cff00ffff" .. zone .. "|r = {")
|
||||
for _, entry in ipairs(overlays) do
|
||||
cf:AddMessage(' "' .. entry .. '",')
|
||||
end
|
||||
cf:AddMessage("}")
|
||||
end
|
||||
|
||||
if not hasData then
|
||||
cf:AddMessage("|cffffb3d9[MapReveal]|r 没有扫描到的新数据可导出。先运行 /nui mapscan")
|
||||
end
|
||||
end
|
||||
|
||||
20
Merchant.lua
20
Merchant.lua
@@ -426,13 +426,18 @@ function MUI:BuyMultiple(index, totalAmount)
|
||||
end
|
||||
|
||||
local name, _, price, batchQty, numAvailable = GetMerchantItemInfo(index)
|
||||
local batchSize = (batchQty and batchQty > 0) and batchQty or 1
|
||||
local numPurchases = math.ceil(totalAmount / batchSize)
|
||||
|
||||
if numAvailable > -1 and totalAmount > numAvailable then
|
||||
totalAmount = numAvailable
|
||||
if numAvailable > -1 and numPurchases > numAvailable then
|
||||
numPurchases = numAvailable
|
||||
end
|
||||
|
||||
for i = 1, totalAmount do
|
||||
BuyMerchantItem(index, 1)
|
||||
while numPurchases > 0 do
|
||||
local batch = numPurchases
|
||||
if batch > 255 then batch = 255 end
|
||||
BuyMerchantItem(index, batch)
|
||||
numPurchases = numPurchases - batch
|
||||
end
|
||||
end
|
||||
|
||||
@@ -612,12 +617,13 @@ local function CreateMerchantButton(parent, id)
|
||||
local name, _, price, quantity, numAvailable = GetMerchantItemInfo(btn.itemIndex)
|
||||
if not name then return end
|
||||
local popupLink = GetMerchantItemLink(btn.itemIndex)
|
||||
local batchSize = (quantity and quantity > 0) and quantity or 1
|
||||
local maxCanAfford = 9999
|
||||
if price and price > 0 then
|
||||
maxCanAfford = math.floor(GetMoney() / price)
|
||||
maxCanAfford = math.floor(GetMoney() / price) * batchSize
|
||||
end
|
||||
if numAvailable > -1 and numAvailable < maxCanAfford then
|
||||
maxCanAfford = numAvailable
|
||||
if numAvailable > -1 and numAvailable * batchSize < maxCanAfford then
|
||||
maxCanAfford = numAvailable * batchSize
|
||||
end
|
||||
if maxCanAfford < 1 then return end
|
||||
EnsureBuyPopup()
|
||||
|
||||
297
Minimap.lua
297
Minimap.lua
@@ -19,6 +19,28 @@ local MAP_STYLES = {
|
||||
{ key = "ss", tex = "Interface\\AddOns\\Nanami-UI\\img\\ss", label = "术士", plateY = -6, textColor = {1, 1, 1} },
|
||||
{ key = "dly", tex = "Interface\\AddOns\\Nanami-UI\\img\\dly", label = "德鲁伊", plateY = -6, textColor = {0.22, 0.13, 0.07} },
|
||||
}
|
||||
local SQUARE_STYLES = {
|
||||
{
|
||||
key = "square1",
|
||||
label = "方形·金",
|
||||
tex = "Interface\\AddOns\\Nanami-UI\\img\\map_f_1",
|
||||
texSize = 512,
|
||||
mapX = 9, mapY = 9, mapW = 486, mapH = 486,
|
||||
zoneOverlay = true,
|
||||
textColor = {0.85, 0.75, 0.55},
|
||||
},
|
||||
{
|
||||
key = "square2",
|
||||
label = "方形·暗",
|
||||
tex = "Interface\\AddOns\\Nanami-UI\\img\\map_f_2",
|
||||
texSize = 512,
|
||||
mapX = 52, mapY = 61, mapW = 418, mapH = 418,
|
||||
zonePlateX = 104, zonePlateY = 18, zonePlateW = 302, zonePlateH = 45,
|
||||
textColor = {0.8, 0.8, 0.8},
|
||||
},
|
||||
}
|
||||
local SQUARE_MASK = "Interface\\BUTTONS\\WHITE8X8"
|
||||
|
||||
local TEX_SIZE = 512
|
||||
local CIRCLE_CX = 256
|
||||
local CIRCLE_CY = 256
|
||||
@@ -47,6 +69,7 @@ local DEFAULTS = {
|
||||
showClock = true,
|
||||
showCoords = true,
|
||||
mapStyle = "auto",
|
||||
mapShape = "square1",
|
||||
posX = -5,
|
||||
posY = -5,
|
||||
mailIconX = nil,
|
||||
@@ -54,7 +77,7 @@ local DEFAULTS = {
|
||||
}
|
||||
|
||||
local container, overlayFrame, overlayTex
|
||||
local zoneFs, clockFs, clockBg, coordFs
|
||||
local zoneFs, clockFs, clockBg, coordFs, zoneBg
|
||||
local built = false
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -93,6 +116,19 @@ local function GetMapTexture()
|
||||
return GetCurrentStyle().tex
|
||||
end
|
||||
|
||||
local function IsSquareShape()
|
||||
local shape = GetDB().mapShape or "square1"
|
||||
return shape == "square1" or shape == "square2"
|
||||
end
|
||||
|
||||
local function GetSquareStyle()
|
||||
local shape = GetDB().mapShape or "square1"
|
||||
for _, s in ipairs(SQUARE_STYLES) do
|
||||
if s.key == shape then return s end
|
||||
end
|
||||
return SQUARE_STYLES[1]
|
||||
end
|
||||
|
||||
local function S(texPx, frameSize)
|
||||
return texPx / TEX_SIZE * frameSize
|
||||
end
|
||||
@@ -103,11 +139,17 @@ end
|
||||
|
||||
local function ApplyPosition()
|
||||
if not container then return end
|
||||
local db = GetDB()
|
||||
local x = tonumber(db.posX) or -5
|
||||
local y = tonumber(db.posY) or -5
|
||||
container:ClearAllPoints()
|
||||
container:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", x, y)
|
||||
local pos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["Minimap"]
|
||||
if pos and pos.point and pos.relativePoint then
|
||||
container:ClearAllPoints()
|
||||
container:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||||
else
|
||||
local db = GetDB()
|
||||
local x = tonumber(db.posX) or -5
|
||||
local y = tonumber(db.posY) or -5
|
||||
container:ClearAllPoints()
|
||||
container:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", x, y)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -178,7 +220,7 @@ local function BuildFrame()
|
||||
built = true
|
||||
|
||||
local fs = FrameSize()
|
||||
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
|
||||
local isSquare = IsSquareShape()
|
||||
|
||||
-- Main container
|
||||
container = CreateFrame("Frame", "SFramesMinimapContainer", UIParent)
|
||||
@@ -192,19 +234,35 @@ local function BuildFrame()
|
||||
-- Reparent the actual minimap into our container
|
||||
Minimap:SetParent(container)
|
||||
Minimap:ClearAllPoints()
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT",
|
||||
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
|
||||
Minimap:SetWidth(mapDiam)
|
||||
Minimap:SetHeight(mapDiam)
|
||||
if isSquare then
|
||||
local sq = GetSquareStyle()
|
||||
local mapW = sq.mapW / sq.texSize * fs
|
||||
local mapH = sq.mapH / sq.texSize * fs
|
||||
local cx = (sq.mapX + sq.mapW / 2) / sq.texSize * fs
|
||||
local cy = (sq.mapY + sq.mapH / 2) / sq.texSize * fs
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT", cx, -cy)
|
||||
Minimap:SetWidth(mapW)
|
||||
Minimap:SetHeight(mapH)
|
||||
else
|
||||
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT",
|
||||
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
|
||||
Minimap:SetWidth(mapDiam)
|
||||
Minimap:SetHeight(mapDiam)
|
||||
end
|
||||
Minimap:SetFrameStrata("LOW")
|
||||
Minimap:SetFrameLevel(2)
|
||||
Minimap:Show()
|
||||
|
||||
if Minimap.SetMaskTexture then
|
||||
Minimap:SetMaskTexture("Textures\\MinimapMask")
|
||||
if isSquare then
|
||||
Minimap:SetMaskTexture(SQUARE_MASK)
|
||||
else
|
||||
Minimap:SetMaskTexture("Textures\\MinimapMask")
|
||||
end
|
||||
end
|
||||
|
||||
-- Decorative overlay (map.tga with transparent circle)
|
||||
-- Decorative overlay (frame texture)
|
||||
overlayFrame = CreateFrame("Frame", nil, container)
|
||||
overlayFrame:SetAllPoints(container)
|
||||
overlayFrame:SetFrameStrata("LOW")
|
||||
@@ -212,22 +270,58 @@ local function BuildFrame()
|
||||
overlayFrame:EnableMouse(false)
|
||||
|
||||
overlayTex = overlayFrame:CreateTexture(nil, "ARTWORK")
|
||||
overlayTex:SetTexture(GetMapTexture())
|
||||
if isSquare then
|
||||
overlayTex:SetTexture(GetSquareStyle().tex)
|
||||
else
|
||||
overlayTex:SetTexture(GetMapTexture())
|
||||
end
|
||||
overlayTex:SetAllPoints(overlayFrame)
|
||||
|
||||
-- Zone name on the scroll plate (horizontally centered on frame)
|
||||
local style = GetCurrentStyle()
|
||||
local pcy = S(PLATE_Y + PLATE_H / 2 + (style.plateY or -6), fs)
|
||||
-- Zone name background (semi-transparent strip for square styles)
|
||||
zoneBg = CreateFrame("Frame", nil, overlayFrame)
|
||||
zoneBg:SetFrameLevel(overlayFrame:GetFrameLevel() - 1)
|
||||
zoneBg:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
})
|
||||
zoneBg:SetBackdropColor(0, 0, 0, 0.5)
|
||||
zoneBg:Hide()
|
||||
|
||||
-- Zone name
|
||||
zoneFs = overlayFrame:CreateFontString(nil, "OVERLAY")
|
||||
zoneFs:SetFont(SFrames:GetFont(), 11, "")
|
||||
zoneFs:SetPoint("CENTER", container, "TOP", 0, -pcy)
|
||||
zoneFs:SetWidth(S(PLATE_W + 60, fs))
|
||||
zoneFs:SetHeight(S(PLATE_H, fs))
|
||||
zoneFs:SetJustifyH("CENTER")
|
||||
zoneFs:SetJustifyV("MIDDLE")
|
||||
local tc = style.textColor or {0.22, 0.13, 0.07}
|
||||
zoneFs:SetTextColor(tc[1], tc[2], tc[3])
|
||||
|
||||
if isSquare then
|
||||
local sq = GetSquareStyle()
|
||||
zoneFs:SetFont(SFrames:GetFont(), 11, "OUTLINE")
|
||||
if sq.zonePlateX then
|
||||
local px = (sq.zonePlateX + sq.zonePlateW / 2) / sq.texSize * fs
|
||||
local py = (sq.zonePlateY + sq.zonePlateH / 2) / sq.texSize * fs
|
||||
zoneFs:SetPoint("CENTER", container, "TOPLEFT", px, -py)
|
||||
zoneFs:SetWidth(sq.zonePlateW / sq.texSize * fs)
|
||||
zoneFs:SetHeight(sq.zonePlateH / sq.texSize * fs)
|
||||
else
|
||||
zoneFs:SetPoint("TOP", Minimap, "TOP", 0, -3)
|
||||
zoneFs:SetWidth(Minimap:GetWidth() * 0.7)
|
||||
zoneFs:SetHeight(18)
|
||||
zoneBg:ClearAllPoints()
|
||||
zoneBg:SetPoint("TOP", Minimap, "TOP", 0, 0)
|
||||
zoneBg:SetWidth(Minimap:GetWidth())
|
||||
zoneBg:SetHeight(20)
|
||||
zoneBg:Show()
|
||||
end
|
||||
local tc = sq.textColor or {1, 1, 1}
|
||||
zoneFs:SetTextColor(tc[1], tc[2], tc[3])
|
||||
else
|
||||
local style = GetCurrentStyle()
|
||||
zoneFs:SetFont(SFrames:GetFont(), 11, "")
|
||||
local pcy = S(PLATE_Y + PLATE_H / 2 + (style.plateY or -6), fs)
|
||||
zoneFs:SetPoint("CENTER", container, "TOP", 0, -pcy)
|
||||
zoneFs:SetWidth(S(PLATE_W + 60, fs))
|
||||
zoneFs:SetHeight(S(PLATE_H, fs))
|
||||
local tc = style.textColor or {0.22, 0.13, 0.07}
|
||||
zoneFs:SetTextColor(tc[1], tc[2], tc[3])
|
||||
end
|
||||
|
||||
-- Clock background (semi-transparent rounded)
|
||||
clockBg = CreateFrame("Frame", nil, overlayFrame)
|
||||
@@ -244,8 +338,12 @@ local function BuildFrame()
|
||||
clockBg:SetBackdropBorderColor(_clkBd[1], _clkBd[2], _clkBd[3], _clkBd[4])
|
||||
clockBg:SetWidth(46)
|
||||
clockBg:SetHeight(18)
|
||||
clockBg:SetPoint("CENTER", container, "BOTTOM", 0,
|
||||
S((TEX_SIZE - CIRCLE_CY - CIRCLE_R) / 2, fs))
|
||||
if isSquare then
|
||||
clockBg:SetPoint("TOP", Minimap, "BOTTOM", 0, -2)
|
||||
else
|
||||
clockBg:SetPoint("CENTER", container, "BOTTOM", 0,
|
||||
S((TEX_SIZE - CIRCLE_CY - CIRCLE_R) / 2, fs))
|
||||
end
|
||||
|
||||
-- Clock text
|
||||
clockFs = clockBg:CreateFontString(nil, "OVERLAY")
|
||||
@@ -255,7 +353,7 @@ local function BuildFrame()
|
||||
local _clkTxt = _A.clockText or { 0.92, 0.84, 0.72 }
|
||||
clockFs:SetTextColor(_clkTxt[1], _clkTxt[2], _clkTxt[3])
|
||||
|
||||
-- Coordinates (inside circle, near bottom)
|
||||
-- Coordinates (inside map area, near bottom)
|
||||
coordFs = overlayFrame:CreateFontString(nil, "OVERLAY")
|
||||
coordFs:SetFont(SFrames:GetFont(), 9, "OUTLINE")
|
||||
coordFs:SetPoint("BOTTOM", Minimap, "BOTTOM", 0, 8)
|
||||
@@ -430,47 +528,114 @@ function MM:Refresh()
|
||||
if not container then return end
|
||||
|
||||
local fs = FrameSize()
|
||||
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
|
||||
local isSquare = IsSquareShape()
|
||||
|
||||
container:SetWidth(fs)
|
||||
container:SetHeight(fs)
|
||||
|
||||
Minimap:ClearAllPoints()
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT",
|
||||
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
|
||||
Minimap:SetWidth(mapDiam)
|
||||
Minimap:SetHeight(mapDiam)
|
||||
|
||||
if zoneFs then
|
||||
local style = GetCurrentStyle()
|
||||
local pcy = S(PLATE_Y + PLATE_H / 2 + (style.plateY or -6), fs)
|
||||
zoneFs:ClearAllPoints()
|
||||
zoneFs:SetPoint("CENTER", container, "TOP", 0, -pcy)
|
||||
zoneFs:SetWidth(S(PLATE_W + 60, fs))
|
||||
local tc = style.textColor or {0.22, 0.13, 0.07}
|
||||
zoneFs:SetTextColor(tc[1], tc[2], tc[3])
|
||||
-- Apply mask
|
||||
if Minimap.SetMaskTexture then
|
||||
if isSquare then
|
||||
Minimap:SetMaskTexture(SQUARE_MASK)
|
||||
else
|
||||
Minimap:SetMaskTexture("Textures\\MinimapMask")
|
||||
end
|
||||
end
|
||||
|
||||
-- Position and size minimap
|
||||
Minimap:ClearAllPoints()
|
||||
if isSquare then
|
||||
local sq = GetSquareStyle()
|
||||
local mapW = sq.mapW / sq.texSize * fs
|
||||
local mapH = sq.mapH / sq.texSize * fs
|
||||
local cx = (sq.mapX + sq.mapW / 2) / sq.texSize * fs
|
||||
local cy = (sq.mapY + sq.mapH / 2) / sq.texSize * fs
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT", cx, -cy)
|
||||
Minimap:SetWidth(mapW)
|
||||
Minimap:SetHeight(mapH)
|
||||
else
|
||||
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT",
|
||||
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
|
||||
Minimap:SetWidth(mapDiam)
|
||||
Minimap:SetHeight(mapDiam)
|
||||
end
|
||||
|
||||
-- Update overlay texture
|
||||
if overlayTex then
|
||||
if isSquare then
|
||||
overlayTex:SetTexture(GetSquareStyle().tex)
|
||||
else
|
||||
overlayTex:SetTexture(GetMapTexture())
|
||||
end
|
||||
end
|
||||
|
||||
-- Update zone text
|
||||
if zoneFs then
|
||||
zoneFs:ClearAllPoints()
|
||||
if isSquare then
|
||||
local sq = GetSquareStyle()
|
||||
zoneFs:SetFont(SFrames:GetFont(), 11, "OUTLINE")
|
||||
if sq.zonePlateX then
|
||||
local px = (sq.zonePlateX + sq.zonePlateW / 2) / sq.texSize * fs
|
||||
local py = (sq.zonePlateY + sq.zonePlateH / 2) / sq.texSize * fs
|
||||
zoneFs:SetPoint("CENTER", container, "TOPLEFT", px, -py)
|
||||
zoneFs:SetWidth(sq.zonePlateW / sq.texSize * fs)
|
||||
zoneFs:SetHeight(sq.zonePlateH / sq.texSize * fs)
|
||||
else
|
||||
zoneFs:SetPoint("TOP", Minimap, "TOP", 0, -3)
|
||||
zoneFs:SetWidth(Minimap:GetWidth() * 0.7)
|
||||
zoneFs:SetHeight(18)
|
||||
end
|
||||
local tc = sq.textColor or {1, 1, 1}
|
||||
zoneFs:SetTextColor(tc[1], tc[2], tc[3])
|
||||
else
|
||||
zoneFs:SetFont(SFrames:GetFont(), 11, "")
|
||||
local style = GetCurrentStyle()
|
||||
local pcy = S(PLATE_Y + PLATE_H / 2 + (style.plateY or -6), fs)
|
||||
zoneFs:SetPoint("CENTER", container, "TOP", 0, -pcy)
|
||||
zoneFs:SetWidth(S(PLATE_W + 60, fs))
|
||||
local tc = style.textColor or {0.22, 0.13, 0.07}
|
||||
zoneFs:SetTextColor(tc[1], tc[2], tc[3])
|
||||
end
|
||||
end
|
||||
|
||||
-- Zone background (semi-transparent strip, only for square styles with zoneOverlay)
|
||||
if zoneBg then
|
||||
if isSquare and GetSquareStyle().zoneOverlay then
|
||||
zoneBg:ClearAllPoints()
|
||||
zoneBg:SetPoint("TOP", Minimap, "TOP", 0, 0)
|
||||
zoneBg:SetWidth(Minimap:GetWidth())
|
||||
zoneBg:SetHeight(20)
|
||||
zoneBg:Show()
|
||||
else
|
||||
zoneBg:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Clock
|
||||
if clockBg then
|
||||
clockBg:ClearAllPoints()
|
||||
clockBg:SetPoint("CENTER", container, "BOTTOM", 0,
|
||||
S((TEX_SIZE - CIRCLE_CY - CIRCLE_R) / 2, fs))
|
||||
if isSquare then
|
||||
clockBg:SetPoint("TOP", Minimap, "BOTTOM", 0, -2)
|
||||
else
|
||||
clockBg:SetPoint("CENTER", container, "BOTTOM", 0,
|
||||
S((TEX_SIZE - CIRCLE_CY - CIRCLE_R) / 2, fs))
|
||||
end
|
||||
end
|
||||
|
||||
-- Coords
|
||||
if coordFs then
|
||||
coordFs:ClearAllPoints()
|
||||
coordFs:SetPoint("BOTTOM", Minimap, "BOTTOM", 0, 8)
|
||||
end
|
||||
|
||||
if overlayTex then
|
||||
overlayTex:SetTexture(GetMapTexture())
|
||||
end
|
||||
|
||||
UpdateZoneText()
|
||||
RepositionIcons()
|
||||
end
|
||||
|
||||
MM.MAP_STYLES = MAP_STYLES
|
||||
MM.SQUARE_STYLES = SQUARE_STYLES
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Shield: re-apply our skin after other addons (ShaguTweaks etc.) touch Minimap
|
||||
@@ -481,24 +646,40 @@ local function ShieldMinimap()
|
||||
if shielded then return end
|
||||
shielded = true
|
||||
|
||||
-- Override any external changes to Minimap parent / position / size
|
||||
if Minimap:GetParent() ~= container then
|
||||
Minimap:SetParent(container)
|
||||
end
|
||||
|
||||
local fs = FrameSize()
|
||||
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
|
||||
local isSquare = IsSquareShape()
|
||||
|
||||
Minimap:ClearAllPoints()
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT",
|
||||
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
|
||||
Minimap:SetWidth(mapDiam)
|
||||
Minimap:SetHeight(mapDiam)
|
||||
if isSquare then
|
||||
local sq = GetSquareStyle()
|
||||
local mapW = sq.mapW / sq.texSize * fs
|
||||
local mapH = sq.mapH / sq.texSize * fs
|
||||
local cx = (sq.mapX + sq.mapW / 2) / sq.texSize * fs
|
||||
local cy = (sq.mapY + sq.mapH / 2) / sq.texSize * fs
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT", cx, -cy)
|
||||
Minimap:SetWidth(mapW)
|
||||
Minimap:SetHeight(mapH)
|
||||
else
|
||||
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
|
||||
Minimap:SetPoint("CENTER", container, "TOPLEFT",
|
||||
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
|
||||
Minimap:SetWidth(mapDiam)
|
||||
Minimap:SetHeight(mapDiam)
|
||||
end
|
||||
Minimap:SetFrameStrata("LOW")
|
||||
Minimap:SetFrameLevel(2)
|
||||
Minimap:Show()
|
||||
|
||||
if Minimap.SetMaskTexture then
|
||||
Minimap:SetMaskTexture("Textures\\MinimapMask")
|
||||
if isSquare then
|
||||
Minimap:SetMaskTexture(SQUARE_MASK)
|
||||
else
|
||||
Minimap:SetMaskTexture("Textures\\MinimapMask")
|
||||
end
|
||||
end
|
||||
|
||||
HideDefaultElements()
|
||||
@@ -587,4 +768,12 @@ function MM:Initialize()
|
||||
if not ok then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI Minimap error: " .. tostring(err) .. "|r")
|
||||
end
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover and container then
|
||||
SFrames.Movers:RegisterMover("Minimap", container, "小地图",
|
||||
"TOPRIGHT", "UIParent", "TOPRIGHT", db.posX or -5, db.posY or -5,
|
||||
function()
|
||||
pcall(RepositionIcons)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,7 +20,9 @@ function SFrames:GetBuffName(buffIndex)
|
||||
SFrames.Tooltip:ClearLines()
|
||||
SFrames.Tooltip:SetPlayerBuff(buffIndex)
|
||||
local nameObj = SFramesScanTooltipTextLeft1
|
||||
return nameObj and nameObj:GetText()
|
||||
local name = nameObj and nameObj:GetText()
|
||||
SFrames.Tooltip:Hide()
|
||||
return name
|
||||
end
|
||||
|
||||
function SFrames:IsBuffHidden(buffIndex)
|
||||
@@ -239,13 +241,19 @@ end
|
||||
|
||||
function MB:ApplyPosition()
|
||||
if not self.buffContainer then return end
|
||||
local db = GetDB()
|
||||
local pos = db.position or "TOPRIGHT"
|
||||
local x = BASE_X + (db.offsetX or 0)
|
||||
local y = BASE_Y + (db.offsetY or 0)
|
||||
|
||||
self.buffContainer:ClearAllPoints()
|
||||
self.buffContainer:SetPoint(pos, UIParent, pos, x, y)
|
||||
local saved = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["MinimapBuffs"]
|
||||
if saved and saved.point and saved.relativePoint then
|
||||
self.buffContainer:ClearAllPoints()
|
||||
self.buffContainer:SetPoint(saved.point, UIParent, saved.relativePoint, saved.xOfs or 0, saved.yOfs or 0)
|
||||
else
|
||||
local db = GetDB()
|
||||
local pos = db.position or "TOPRIGHT"
|
||||
local x = BASE_X + (db.offsetX or 0)
|
||||
local y = BASE_Y + (db.offsetY or 0)
|
||||
self.buffContainer:ClearAllPoints()
|
||||
self.buffContainer:SetPoint(pos, UIParent, pos, x, y)
|
||||
end
|
||||
|
||||
self:AnchorDebuffs()
|
||||
end
|
||||
@@ -478,6 +486,7 @@ function MB:UpdateDebuffs()
|
||||
SFrames.Tooltip:ClearLines()
|
||||
SFrames.Tooltip:SetPlayerBuff(buffIndex)
|
||||
local dTypeStr = SFramesScanTooltipTextRight1 and SFramesScanTooltipTextRight1:GetText()
|
||||
SFrames.Tooltip:Hide()
|
||||
if dTypeStr and dTypeStr ~= "" then debuffType = dTypeStr end
|
||||
|
||||
local c = DEBUFF_TYPE_COLORS[debuffType] or DEBUFF_DEFAULT_COLOR
|
||||
@@ -662,4 +671,16 @@ function MB:Initialize()
|
||||
end)
|
||||
|
||||
self:UpdateBuffs()
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover then
|
||||
if self.buffContainer then
|
||||
SFrames.Movers:RegisterMover("MinimapBuffs", self.buffContainer, "Buff",
|
||||
"TOPRIGHT", "UIParent", "TOPRIGHT", BASE_X + (db.offsetX or 0), BASE_Y + (db.offsetY or 0),
|
||||
function() MB:AnchorDebuffs() end)
|
||||
end
|
||||
if self.debuffContainer then
|
||||
SFrames.Movers:RegisterMover("MinimapDebuffs", self.debuffContainer, "Debuff",
|
||||
"TOPRIGHT", "UIParent", "TOPRIGHT", BASE_X + (db.offsetX or 0), BASE_Y - 50)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
998
Movers.lua
Normal file
998
Movers.lua
Normal file
@@ -0,0 +1,998 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Nanami-UI: Layout Mode (Mover System)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
SFrames.Movers = {}
|
||||
|
||||
local M = SFrames.Movers
|
||||
local registry = {}
|
||||
local moverFrames = {}
|
||||
local gridFrame = nil
|
||||
local controlBar = nil
|
||||
local overlayFrame = nil
|
||||
local isLayoutMode = false
|
||||
|
||||
local GRID_SPACING = 50
|
||||
local SNAP_THRESHOLD = 10
|
||||
|
||||
local MOVER_BACKDROP = {
|
||||
bgFile = "Interface\\Buttons\\WHITE8x8",
|
||||
edgeFile = "Interface\\Buttons\\WHITE8x8",
|
||||
edgeSize = 1,
|
||||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||||
}
|
||||
local MOVER_BACKDROP_2PX = {
|
||||
bgFile = "Interface\\Buttons\\WHITE8x8",
|
||||
edgeFile = "Interface\\Buttons\\WHITE8x8",
|
||||
edgeSize = 2,
|
||||
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Theme helper
|
||||
--------------------------------------------------------------------------------
|
||||
local function T()
|
||||
return SFrames.ActiveTheme or {}
|
||||
end
|
||||
|
||||
local function Font()
|
||||
return (SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Config helpers
|
||||
--------------------------------------------------------------------------------
|
||||
local function GetLayoutCfg()
|
||||
if not SFramesDB then SFramesDB = {} end
|
||||
if type(SFramesDB.layoutMode) ~= "table" then
|
||||
SFramesDB.layoutMode = {
|
||||
snapEnabled = true,
|
||||
snapThreshold = SNAP_THRESHOLD,
|
||||
showGrid = true,
|
||||
}
|
||||
end
|
||||
return SFramesDB.layoutMode
|
||||
end
|
||||
|
||||
local function EnsurePositions()
|
||||
if not SFramesDB then SFramesDB = {} end
|
||||
if not SFramesDB.Positions then SFramesDB.Positions = {} end
|
||||
return SFramesDB.Positions
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Snap logic
|
||||
--------------------------------------------------------------------------------
|
||||
local function GetFrameEdges(frame)
|
||||
local l = frame:GetLeft() or 0
|
||||
local r = frame:GetRight() or 0
|
||||
local tp = frame:GetTop() or 0
|
||||
local b = frame:GetBottom() or 0
|
||||
return l, r, tp, b, (l + r) / 2, (tp + b) / 2
|
||||
end
|
||||
|
||||
local function ApplySnap(mover)
|
||||
local cfg = GetLayoutCfg()
|
||||
if not cfg.snapEnabled then return end
|
||||
if IsShiftKeyDown() then return end
|
||||
|
||||
local threshold = cfg.snapThreshold or SNAP_THRESHOLD
|
||||
local ml, mr, mt, mb, mcx, mcy = GetFrameEdges(mover)
|
||||
local mw, mh = mr - ml, mt - mb
|
||||
|
||||
local screenW = UIParent:GetRight() or UIParent:GetWidth()
|
||||
local screenH = UIParent:GetTop() or UIParent:GetHeight()
|
||||
local screenCX, screenCY = screenW / 2, screenH / 2
|
||||
|
||||
local snapX, snapY = nil, nil
|
||||
local bestDX, bestDY = threshold + 1, threshold + 1
|
||||
|
||||
if math.abs(ml) < bestDX then bestDX = math.abs(ml); snapX = 0 end
|
||||
if math.abs(mr - screenW) < bestDX then bestDX = math.abs(mr - screenW); snapX = screenW - mw end
|
||||
if math.abs(mt - screenH) < bestDY then bestDY = math.abs(mt - screenH); snapY = screenH - mh end
|
||||
if math.abs(mb) < bestDY then bestDY = math.abs(mb); snapY = 0 end
|
||||
|
||||
if math.abs(mcx - screenCX) < bestDX then bestDX = math.abs(mcx - screenCX); snapX = screenCX - mw / 2 end
|
||||
if math.abs(mcy - screenCY) < bestDY then bestDY = math.abs(mcy - screenCY); snapY = screenCY - mh / 2 end
|
||||
|
||||
for name, _ in pairs(registry) do
|
||||
local other = moverFrames[name]
|
||||
if other and other ~= mover and other:IsShown() then
|
||||
local ol, or2, ot, ob, ocx, ocy = GetFrameEdges(other)
|
||||
local px = {
|
||||
{ ml, or2, 0 }, { mr, ol, -mw }, { ml, ol, 0 },
|
||||
{ mr, or2, -mw }, { mcx, ocx, -mw / 2 },
|
||||
}
|
||||
for _, p in ipairs(px) do
|
||||
local d = math.abs(p[1] - p[2])
|
||||
if d < bestDX then bestDX = d; snapX = p[2] + p[3] end
|
||||
end
|
||||
local py = {
|
||||
{ math.abs(mb - ot), ot }, { math.abs(mt - ob), ob - mh },
|
||||
{ math.abs(mt - ot), ot - mh }, { math.abs(mb - ob), ob },
|
||||
{ math.abs(mcy - ocy), ocy - mh / 2 },
|
||||
}
|
||||
for _, p in ipairs(py) do
|
||||
if p[1] < bestDY then bestDY = p[1]; snapY = p[2] end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if bestDX <= threshold and snapX then
|
||||
mover:ClearAllPoints()
|
||||
local curB = (bestDY <= threshold and snapY) or (mover:GetBottom() or 0)
|
||||
mover:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", snapX, curB)
|
||||
return
|
||||
end
|
||||
if bestDY <= threshold and snapY then
|
||||
mover:ClearAllPoints()
|
||||
mover:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", mover:GetLeft() or 0, snapY)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Position save / load
|
||||
--------------------------------------------------------------------------------
|
||||
local function SaveMoverPosition(name, mover)
|
||||
local positions = EnsurePositions()
|
||||
local l = mover:GetLeft()
|
||||
local b = mover:GetBottom()
|
||||
local screenW = UIParent:GetRight() or UIParent:GetWidth()
|
||||
local screenH = UIParent:GetTop() or UIParent:GetHeight()
|
||||
if not l or not b then return end
|
||||
|
||||
local cx = l + (mover:GetWidth() or 0) / 2
|
||||
local cy = b + (mover:GetHeight() or 0) / 2
|
||||
|
||||
local point, relPoint, xOfs, yOfs
|
||||
if cx < screenW / 3 then
|
||||
if cy > screenH * 2 / 3 then
|
||||
point = "TOPLEFT"; relPoint = "TOPLEFT"
|
||||
xOfs = l; yOfs = -(screenH - (b + (mover:GetHeight() or 0)))
|
||||
elseif cy < screenH / 3 then
|
||||
point = "BOTTOMLEFT"; relPoint = "BOTTOMLEFT"
|
||||
xOfs = l; yOfs = b
|
||||
else
|
||||
point = "LEFT"; relPoint = "LEFT"
|
||||
xOfs = l; yOfs = cy - screenH / 2
|
||||
end
|
||||
elseif cx > screenW * 2 / 3 then
|
||||
local r = mover:GetRight() or 0
|
||||
if cy > screenH * 2 / 3 then
|
||||
point = "TOPRIGHT"; relPoint = "TOPRIGHT"
|
||||
xOfs = r - screenW; yOfs = -(screenH - (b + (mover:GetHeight() or 0)))
|
||||
elseif cy < screenH / 3 then
|
||||
point = "BOTTOMRIGHT"; relPoint = "BOTTOMRIGHT"
|
||||
xOfs = r - screenW; yOfs = b
|
||||
else
|
||||
point = "RIGHT"; relPoint = "RIGHT"
|
||||
xOfs = r - screenW; yOfs = cy - screenH / 2
|
||||
end
|
||||
else
|
||||
if cy > screenH * 2 / 3 then
|
||||
point = "TOP"; relPoint = "TOP"
|
||||
xOfs = cx - screenW / 2; yOfs = -(screenH - (b + (mover:GetHeight() or 0)))
|
||||
elseif cy < screenH / 3 then
|
||||
point = "BOTTOM"; relPoint = "BOTTOM"
|
||||
xOfs = cx - screenW / 2; yOfs = b
|
||||
else
|
||||
point = "CENTER"; relPoint = "CENTER"
|
||||
xOfs = cx - screenW / 2; yOfs = cy - screenH / 2
|
||||
end
|
||||
end
|
||||
|
||||
positions[name] = { point = point, relativePoint = relPoint, xOfs = xOfs, yOfs = yOfs }
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Sync mover <-> actual frame
|
||||
--------------------------------------------------------------------------------
|
||||
local function UpdateCoordText(mover)
|
||||
if not mover or not mover._coordText then return end
|
||||
local l = mover:GetLeft()
|
||||
local b = mover:GetBottom()
|
||||
if l and b then
|
||||
mover._coordText:SetText(string.format("%.0f, %.0f", l, b))
|
||||
end
|
||||
end
|
||||
|
||||
local function SyncMoverToFrame(name)
|
||||
local entry = registry[name]
|
||||
local mover = moverFrames[name]
|
||||
if not entry or not mover then return end
|
||||
local frame = entry.frame
|
||||
if not frame then return end
|
||||
|
||||
local scale = frame:GetEffectiveScale() / UIParent:GetEffectiveScale()
|
||||
local w = (frame:GetWidth() or 100) * scale
|
||||
local h = (frame:GetHeight() or 50) * scale
|
||||
|
||||
mover:SetWidth(math.max(w, 72))
|
||||
mover:SetHeight(math.max(h, 40))
|
||||
|
||||
local l = frame:GetLeft()
|
||||
local b = frame:GetBottom()
|
||||
if l and b then
|
||||
mover:ClearAllPoints()
|
||||
mover:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", l * scale, b * scale)
|
||||
else
|
||||
local positions = EnsurePositions()
|
||||
local pos = positions[name]
|
||||
if pos and pos.point and pos.relativePoint then
|
||||
local tempF = CreateFrame("Frame", nil, UIParent)
|
||||
tempF:SetWidth(w)
|
||||
tempF:SetHeight(h)
|
||||
tempF:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||||
local tl = tempF:GetLeft()
|
||||
local tb = tempF:GetBottom()
|
||||
if tl and tb then
|
||||
mover:ClearAllPoints()
|
||||
mover:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", tl, tb)
|
||||
end
|
||||
tempF:Hide()
|
||||
else
|
||||
mover:ClearAllPoints()
|
||||
local relTo = _G[entry.defaultRelativeTo] or UIParent
|
||||
mover:SetPoint(entry.defaultPoint, relTo, entry.defaultRelPoint,
|
||||
entry.defaultX or 0, entry.defaultY or 0)
|
||||
end
|
||||
end
|
||||
UpdateCoordText(mover)
|
||||
end
|
||||
|
||||
local function SyncFrameToMover(name)
|
||||
local entry = registry[name]
|
||||
local mover = moverFrames[name]
|
||||
if not entry or not mover then return end
|
||||
local frame = entry.frame
|
||||
if not frame then return end
|
||||
|
||||
local moverL = mover:GetLeft() or 0
|
||||
local moverB = mover:GetBottom() or 0
|
||||
|
||||
SaveMoverPosition(name, mover)
|
||||
|
||||
local positions = EnsurePositions()
|
||||
local pos = positions[name]
|
||||
if pos then
|
||||
frame:ClearAllPoints()
|
||||
frame:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||||
|
||||
local scale = frame:GetEffectiveScale() / UIParent:GetEffectiveScale()
|
||||
local newL = frame:GetLeft() or 0
|
||||
local newB = frame:GetBottom() or 0
|
||||
local dL = newL * scale - moverL
|
||||
local dB = newB * scale - moverB
|
||||
if math.abs(dL) > 2 or math.abs(dB) > 2 then
|
||||
SFrames:Print(string.format(
|
||||
"|cffff6666[位置偏差]|r |cffaaddff%s|r anchor=%s ofs=(%.1f,%.1f) dL=%.1f dB=%.1f scale=%.2f",
|
||||
entry.label or name, pos.point, pos.xOfs or 0, pos.yOfs or 0, dL, dB, scale))
|
||||
end
|
||||
end
|
||||
|
||||
if entry.onMoved then entry.onMoved() end
|
||||
UpdateCoordText(mover)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Nudge helper
|
||||
--------------------------------------------------------------------------------
|
||||
local function NudgeMover(name, dx, dy)
|
||||
local mover = moverFrames[name]
|
||||
if not mover then return end
|
||||
local l = mover:GetLeft()
|
||||
local b = mover:GetBottom()
|
||||
if not l or not b then return end
|
||||
mover:ClearAllPoints()
|
||||
mover:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", l + dx, b + dy)
|
||||
SyncFrameToMover(name)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Pixel-art triangle arrow helper
|
||||
-- Draws a triangle using 4 horizontal/vertical strips for a clean geometric look
|
||||
--------------------------------------------------------------------------------
|
||||
local function CreateTriangle(parent, direction, r, g, b, a)
|
||||
local strips = {}
|
||||
|
||||
if direction == "UP" then
|
||||
local widths = { 2, 5, 8, 11 }
|
||||
local heights = { 2, 2, 2, 2 }
|
||||
local yOfs = { 3, 1, -1, -3 }
|
||||
for i = 1, 4 do
|
||||
local s = parent:CreateTexture(nil, "OVERLAY")
|
||||
s:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
s:SetWidth(widths[i])
|
||||
s:SetHeight(heights[i])
|
||||
s:SetPoint("CENTER", parent, "CENTER", 0, yOfs[i])
|
||||
s:SetVertexColor(r, g, b, a)
|
||||
table.insert(strips, s)
|
||||
end
|
||||
elseif direction == "DOWN" then
|
||||
local widths = { 11, 8, 5, 2 }
|
||||
local heights = { 2, 2, 2, 2 }
|
||||
local yOfs = { 3, 1, -1, -3 }
|
||||
for i = 1, 4 do
|
||||
local s = parent:CreateTexture(nil, "OVERLAY")
|
||||
s:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
s:SetWidth(widths[i])
|
||||
s:SetHeight(heights[i])
|
||||
s:SetPoint("CENTER", parent, "CENTER", 0, yOfs[i])
|
||||
s:SetVertexColor(r, g, b, a)
|
||||
table.insert(strips, s)
|
||||
end
|
||||
elseif direction == "LEFT" then
|
||||
local widths = { 2, 2, 2, 2 }
|
||||
local heights = { 2, 5, 8, 11 }
|
||||
local xOfs = { -3, -1, 1, 3 }
|
||||
for i = 1, 4 do
|
||||
local s = parent:CreateTexture(nil, "OVERLAY")
|
||||
s:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
s:SetWidth(widths[i])
|
||||
s:SetHeight(heights[i])
|
||||
s:SetPoint("CENTER", parent, "CENTER", xOfs[i], 0)
|
||||
s:SetVertexColor(r, g, b, a)
|
||||
table.insert(strips, s)
|
||||
end
|
||||
elseif direction == "RIGHT" then
|
||||
local widths = { 2, 2, 2, 2 }
|
||||
local heights = { 11, 8, 5, 2 }
|
||||
local xOfs = { -3, -1, 1, 3 }
|
||||
for i = 1, 4 do
|
||||
local s = parent:CreateTexture(nil, "OVERLAY")
|
||||
s:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
s:SetWidth(widths[i])
|
||||
s:SetHeight(heights[i])
|
||||
s:SetPoint("CENTER", parent, "CENTER", xOfs[i], 0)
|
||||
s:SetVertexColor(r, g, b, a)
|
||||
table.insert(strips, s)
|
||||
end
|
||||
end
|
||||
|
||||
return strips
|
||||
end
|
||||
|
||||
local function SetTriangleColor(strips, r, g, b, a)
|
||||
for _, s in ipairs(strips) do
|
||||
s:SetVertexColor(r, g, b, a)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Arrow nudge button (triangle-based)
|
||||
--------------------------------------------------------------------------------
|
||||
local ARROW_SIZE = 16
|
||||
local ARROW_REPEAT_DELAY = 0.45
|
||||
local ARROW_REPEAT_RATE = 0.04
|
||||
|
||||
local function CreateArrowButton(parent, moverName, anchor, ofsX, ofsY, direction, dx, dy)
|
||||
local btn = CreateFrame("Button", nil, parent)
|
||||
btn:SetWidth(ARROW_SIZE)
|
||||
btn:SetHeight(ARROW_SIZE)
|
||||
btn:SetPoint(anchor, parent, anchor, ofsX, ofsY)
|
||||
btn:SetFrameLevel(parent:GetFrameLevel() + 5)
|
||||
btn:SetMovable(true)
|
||||
btn:RegisterForDrag("LeftButton")
|
||||
|
||||
local th = T()
|
||||
local dimC = th.dimText or { 0.5, 0.5, 0.55 }
|
||||
local accentC = th.accent or { 1, 0.5, 0.8 }
|
||||
|
||||
local bgTex = btn:CreateTexture(nil, "BACKGROUND")
|
||||
bgTex:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
bgTex:SetAllPoints(btn)
|
||||
bgTex:SetVertexColor(0, 0, 0, 0)
|
||||
btn._bg = bgTex
|
||||
|
||||
local tri = CreateTriangle(btn, direction, dimC[1], dimC[2], dimC[3], 0.55)
|
||||
btn._tri = tri
|
||||
|
||||
btn._elapsed = 0
|
||||
btn._held = false
|
||||
btn._dragging = false
|
||||
btn._delay = ARROW_REPEAT_DELAY
|
||||
|
||||
btn:SetScript("OnClick", function()
|
||||
if not this._dragging then
|
||||
NudgeMover(moverName, dx, dy)
|
||||
end
|
||||
this._dragging = false
|
||||
end)
|
||||
btn:SetScript("OnMouseDown", function()
|
||||
this._held = true
|
||||
this._dragging = false
|
||||
this._elapsed = 0
|
||||
this._delay = ARROW_REPEAT_DELAY
|
||||
end)
|
||||
btn:SetScript("OnMouseUp", function()
|
||||
this._held = false
|
||||
end)
|
||||
btn:SetScript("OnDragStart", function()
|
||||
this._held = false
|
||||
this._dragging = true
|
||||
parent:StartMoving()
|
||||
end)
|
||||
btn:SetScript("OnDragStop", function()
|
||||
parent:StopMovingOrSizing()
|
||||
ApplySnap(parent)
|
||||
SyncFrameToMover(moverName)
|
||||
this._dragging = false
|
||||
end)
|
||||
btn:SetScript("OnUpdate", function()
|
||||
if not this._held then return end
|
||||
this._elapsed = this._elapsed + arg1
|
||||
if this._elapsed >= this._delay then
|
||||
this._elapsed = 0
|
||||
this._delay = ARROW_REPEAT_RATE
|
||||
NudgeMover(moverName, dx, dy)
|
||||
end
|
||||
end)
|
||||
btn:SetScript("OnEnter", function()
|
||||
this._bg:SetVertexColor(accentC[1], accentC[2], accentC[3], 0.18)
|
||||
SetTriangleColor(this._tri, 1, 1, 1, 1)
|
||||
if parent._SetHover then parent:_SetHover(true) end
|
||||
end)
|
||||
btn:SetScript("OnLeave", function()
|
||||
this._held = false
|
||||
this._bg:SetVertexColor(0, 0, 0, 0)
|
||||
SetTriangleColor(this._tri, dimC[1], dimC[2], dimC[3], 0.55)
|
||||
if parent._SetHover then parent:_SetHover(false) end
|
||||
end)
|
||||
|
||||
return btn
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Create individual mover frame
|
||||
--------------------------------------------------------------------------------
|
||||
local function CreateMoverFrame(name, entry)
|
||||
if moverFrames[name] then return moverFrames[name] end
|
||||
|
||||
local mover = CreateFrame("Button", "SFramesMover_" .. name, UIParent)
|
||||
mover:SetFrameStrata("FULLSCREEN")
|
||||
mover:SetFrameLevel(100)
|
||||
mover:SetWidth(100)
|
||||
mover:SetHeight(40)
|
||||
mover:SetClampedToScreen(true)
|
||||
mover:SetMovable(true)
|
||||
mover:EnableMouse(true)
|
||||
mover:RegisterForDrag("LeftButton")
|
||||
mover:RegisterForClicks("RightButtonUp")
|
||||
|
||||
mover:SetBackdrop(MOVER_BACKDROP_2PX)
|
||||
|
||||
local th = T()
|
||||
local secBg = th.sectionBg or { 0.12, 0.10, 0.18, 0.82 }
|
||||
local accent = th.accent or { 1, 0.5, 0.8, 0.98 }
|
||||
local accentLine = th.accentLine or { 0.6, 0.9, 1, 0.9 }
|
||||
|
||||
mover:SetBackdropColor(secBg[1], secBg[2], secBg[3], secBg[4] or 0.82)
|
||||
mover:SetBackdropBorderColor(accent[1], accent[2], accent[3], 0.6)
|
||||
|
||||
-- Top accent stripe
|
||||
local stripe = mover:CreateTexture(nil, "ARTWORK")
|
||||
stripe:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
stripe:SetHeight(2)
|
||||
stripe:SetPoint("TOPLEFT", mover, "TOPLEFT", 2, -2)
|
||||
stripe:SetPoint("TOPRIGHT", mover, "TOPRIGHT", -2, -2)
|
||||
stripe:SetVertexColor(accentLine[1], accentLine[2], accentLine[3], accentLine[4] or 0.9)
|
||||
mover._stripe = stripe
|
||||
|
||||
-- Label
|
||||
local label = mover:CreateFontString(nil, "OVERLAY")
|
||||
label:SetFont(Font(), 11, "OUTLINE")
|
||||
label:SetPoint("CENTER", mover, "CENTER", 0, 5)
|
||||
label:SetText(entry.label or name)
|
||||
local titleC = th.title or { 1, 0.9, 1 }
|
||||
label:SetTextColor(titleC[1], titleC[2], titleC[3], 1)
|
||||
mover._label = label
|
||||
|
||||
-- Coordinate readout
|
||||
local coord = mover:CreateFontString(nil, "OVERLAY")
|
||||
coord:SetFont(Font(), 9, "OUTLINE")
|
||||
coord:SetPoint("CENTER", mover, "CENTER", 0, -7)
|
||||
local dimC = th.dimText or { 0.5, 0.5, 0.55 }
|
||||
coord:SetTextColor(dimC[1], dimC[2], dimC[3], 0.9)
|
||||
coord:SetText("")
|
||||
mover._coordText = coord
|
||||
|
||||
-- Triangle arrow buttons
|
||||
CreateArrowButton(mover, name, "TOP", 0, -1, "UP", 0, 1)
|
||||
CreateArrowButton(mover, name, "BOTTOM", 0, 1, "DOWN", 0, -1)
|
||||
CreateArrowButton(mover, name, "LEFT", 1, 0, "LEFT", -1, 0)
|
||||
CreateArrowButton(mover, name, "RIGHT", -1, 0, "RIGHT", 1, 0)
|
||||
|
||||
-- Hover state manager (tracks child enter/leave)
|
||||
local hoverCount = 0
|
||||
function mover:_SetHover(isEnter)
|
||||
if isEnter then
|
||||
hoverCount = hoverCount + 1
|
||||
else
|
||||
hoverCount = hoverCount - 1
|
||||
if hoverCount < 0 then hoverCount = 0 end
|
||||
end
|
||||
if hoverCount > 0 then
|
||||
self:SetBackdropBorderColor(1, 1, 1, 0.95)
|
||||
stripe:SetVertexColor(1, 1, 1, 1)
|
||||
else
|
||||
self:SetBackdropBorderColor(accent[1], accent[2], accent[3], 0.6)
|
||||
stripe:SetVertexColor(accentLine[1], accentLine[2], accentLine[3], accentLine[4] or 0.9)
|
||||
end
|
||||
end
|
||||
|
||||
mover:SetScript("OnDragStart", function()
|
||||
this:StartMoving()
|
||||
end)
|
||||
|
||||
mover:SetScript("OnDragStop", function()
|
||||
this:StopMovingOrSizing()
|
||||
ApplySnap(this)
|
||||
SyncFrameToMover(name)
|
||||
end)
|
||||
|
||||
mover:SetScript("OnClick", function()
|
||||
if arg1 == "RightButton" then
|
||||
M:ResetMover(name)
|
||||
end
|
||||
end)
|
||||
|
||||
mover:SetScript("OnEnter", function()
|
||||
this:_SetHover(true)
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
GameTooltip:ClearLines()
|
||||
GameTooltip:AddLine(entry.label or name, 1, 0.84, 0.94)
|
||||
GameTooltip:AddDoubleLine("拖拽", "移动位置", 0.7, 0.7, 0.7, 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddDoubleLine("箭头", "微调 1 像素", 0.7, 0.7, 0.7, 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddDoubleLine("右键", "重置位置", 0.7, 0.7, 0.7, 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddDoubleLine("Shift+拖拽", "禁用磁吸", 0.7, 0.7, 0.7, 0.7, 0.7, 0.7)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
|
||||
mover:SetScript("OnLeave", function()
|
||||
this:_SetHover(false)
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
|
||||
mover:Hide()
|
||||
moverFrames[name] = mover
|
||||
return mover
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Dark overlay
|
||||
--------------------------------------------------------------------------------
|
||||
local function CreateOverlay()
|
||||
if overlayFrame then return overlayFrame end
|
||||
|
||||
overlayFrame = CreateFrame("Frame", "SFramesLayoutOverlay", UIParent)
|
||||
overlayFrame:SetFrameStrata("DIALOG")
|
||||
overlayFrame:SetFrameLevel(0)
|
||||
overlayFrame:SetAllPoints(UIParent)
|
||||
overlayFrame:EnableMouse(false)
|
||||
|
||||
local bg = overlayFrame:CreateTexture(nil, "BACKGROUND")
|
||||
bg:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
bg:SetAllPoints(overlayFrame)
|
||||
bg:SetVertexColor(0, 0, 0, 0.45)
|
||||
overlayFrame._bg = bg
|
||||
|
||||
overlayFrame:Hide()
|
||||
return overlayFrame
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Grid overlay
|
||||
--------------------------------------------------------------------------------
|
||||
local function CreateGrid()
|
||||
if gridFrame then return gridFrame end
|
||||
|
||||
gridFrame = CreateFrame("Frame", "SFramesLayoutGrid", UIParent)
|
||||
gridFrame:SetFrameStrata("DIALOG")
|
||||
gridFrame:SetFrameLevel(1)
|
||||
gridFrame:SetAllPoints(UIParent)
|
||||
gridFrame:EnableMouse(false)
|
||||
gridFrame._lines = {}
|
||||
|
||||
local th = T()
|
||||
local axisColor = th.accent or { 1, 0.5, 0.8 }
|
||||
local lineColor = th.dimText or { 0.5, 0.5, 0.55 }
|
||||
|
||||
local function MakeLine(r, g, b, a)
|
||||
local tex = gridFrame:CreateTexture(nil, "BACKGROUND")
|
||||
tex:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
tex:SetVertexColor(r, g, b, a)
|
||||
table.insert(gridFrame._lines, tex)
|
||||
return tex
|
||||
end
|
||||
|
||||
local screenW = UIParent:GetRight() or UIParent:GetWidth()
|
||||
local screenH = UIParent:GetTop() or UIParent:GetHeight()
|
||||
local halfW = math.floor(screenW / 2)
|
||||
local halfH = math.floor(screenH / 2)
|
||||
|
||||
local cv = MakeLine(axisColor[1], axisColor[2], axisColor[3], 0.45)
|
||||
cv:SetWidth(1)
|
||||
cv:SetPoint("TOP", gridFrame, "TOP", 0, 0)
|
||||
cv:SetPoint("BOTTOM", gridFrame, "BOTTOM", 0, 0)
|
||||
|
||||
local ch = MakeLine(axisColor[1], axisColor[2], axisColor[3], 0.45)
|
||||
ch:SetHeight(1)
|
||||
ch:SetPoint("LEFT", gridFrame, "LEFT", 0, 0)
|
||||
ch:SetPoint("RIGHT", gridFrame, "RIGHT", 0, 0)
|
||||
|
||||
local i = GRID_SPACING
|
||||
while i < halfW do
|
||||
local vl = MakeLine(lineColor[1], lineColor[2], lineColor[3], 0.10)
|
||||
vl:SetWidth(1)
|
||||
vl:SetPoint("TOP", gridFrame, "TOP", -i, 0)
|
||||
vl:SetPoint("BOTTOM", gridFrame, "BOTTOM", -i, 0)
|
||||
|
||||
local vr = MakeLine(lineColor[1], lineColor[2], lineColor[3], 0.10)
|
||||
vr:SetWidth(1)
|
||||
vr:SetPoint("TOP", gridFrame, "TOP", i, 0)
|
||||
vr:SetPoint("BOTTOM", gridFrame, "BOTTOM", i, 0)
|
||||
i = i + GRID_SPACING
|
||||
end
|
||||
|
||||
i = GRID_SPACING
|
||||
while i < halfH do
|
||||
local ha = MakeLine(lineColor[1], lineColor[2], lineColor[3], 0.10)
|
||||
ha:SetHeight(1)
|
||||
ha:SetPoint("LEFT", gridFrame, "LEFT", 0, i)
|
||||
ha:SetPoint("RIGHT", gridFrame, "RIGHT", 0, i)
|
||||
|
||||
local hb = MakeLine(lineColor[1], lineColor[2], lineColor[3], 0.10)
|
||||
hb:SetHeight(1)
|
||||
hb:SetPoint("LEFT", gridFrame, "LEFT", 0, -i)
|
||||
hb:SetPoint("RIGHT", gridFrame, "RIGHT", 0, -i)
|
||||
i = i + GRID_SPACING
|
||||
end
|
||||
|
||||
gridFrame:Hide()
|
||||
return gridFrame
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Rounded button helper (for control bar) – matches UI-wide rounded style
|
||||
--------------------------------------------------------------------------------
|
||||
local ROUND_BACKDROP = {
|
||||
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 function MakeControlButton(parent, text, width, xOfs, onClick)
|
||||
local th = T()
|
||||
local btnBg = th.buttonBg or { 0.16, 0.12, 0.22, 0.94 }
|
||||
local btnBd = th.buttonBorder or { 0.35, 0.30, 0.50, 0.90 }
|
||||
local btnHBg = th.buttonHoverBg or { 0.22, 0.18, 0.30, 0.96 }
|
||||
local btnDnBg = th.buttonDownBg or { 0.08, 0.04, 0.06, 0.95 }
|
||||
local btnText = th.buttonText or { 0.85, 0.82, 0.92 }
|
||||
local btnHTxt = th.buttonActiveText or { 1, 0.92, 0.96 }
|
||||
|
||||
local btn = CreateFrame("Button", nil, parent)
|
||||
btn:SetWidth(width)
|
||||
btn:SetHeight(26)
|
||||
btn:SetPoint("LEFT", parent, "LEFT", xOfs, 0)
|
||||
btn:SetBackdrop(ROUND_BACKDROP)
|
||||
btn:SetBackdropColor(btnBg[1], btnBg[2], btnBg[3], btnBg[4] or 0.94)
|
||||
btn:SetBackdropBorderColor(btnBd[1], btnBd[2], btnBd[3], btnBd[4] or 0.90)
|
||||
|
||||
local fs = btn:CreateFontString(nil, "OVERLAY")
|
||||
fs:SetFont(Font(), 10, "OUTLINE")
|
||||
fs:SetPoint("CENTER", 0, 0)
|
||||
fs:SetText(text)
|
||||
fs:SetTextColor(btnText[1], btnText[2], btnText[3], 1)
|
||||
btn._text = fs
|
||||
|
||||
btn:SetScript("OnClick", onClick)
|
||||
btn:SetScript("OnEnter", function()
|
||||
this:SetBackdropColor(btnHBg[1], btnHBg[2], btnHBg[3], btnHBg[4] or 0.96)
|
||||
local a = th.accent or { 1, 0.5, 0.8 }
|
||||
this:SetBackdropBorderColor(a[1], a[2], a[3], 0.95)
|
||||
this._text:SetTextColor(btnHTxt[1], btnHTxt[2], btnHTxt[3], 1)
|
||||
end)
|
||||
btn:SetScript("OnLeave", function()
|
||||
this:SetBackdropColor(btnBg[1], btnBg[2], btnBg[3], btnBg[4] or 0.94)
|
||||
this:SetBackdropBorderColor(btnBd[1], btnBd[2], btnBd[3], btnBd[4] or 0.90)
|
||||
this._text:SetTextColor(btnText[1], btnText[2], btnText[3], 1)
|
||||
end)
|
||||
btn:SetScript("OnMouseDown", function()
|
||||
this:SetBackdropColor(btnDnBg[1], btnDnBg[2], btnDnBg[3], btnDnBg[4] or 0.95)
|
||||
end)
|
||||
btn:SetScript("OnMouseUp", function()
|
||||
this:SetBackdropColor(btnHBg[1], btnHBg[2], btnHBg[3], btnHBg[4] or 0.96)
|
||||
end)
|
||||
|
||||
return btn
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Control bar
|
||||
--------------------------------------------------------------------------------
|
||||
local function CreateControlBar()
|
||||
if controlBar then return controlBar end
|
||||
|
||||
local th = T()
|
||||
local panelBg = th.headerBg or th.panelBg or { 0.08, 0.06, 0.12, 0.98 }
|
||||
local panelBd = th.panelBorder or { 0.35, 0.30, 0.50, 0.90 }
|
||||
local accent = th.accent or { 1, 0.5, 0.8, 0.98 }
|
||||
local titleC = th.title or { 1, 0.88, 1 }
|
||||
|
||||
controlBar = CreateFrame("Frame", "SFramesLayoutControlBar", UIParent)
|
||||
controlBar:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
controlBar:SetFrameLevel(200)
|
||||
controlBar:SetWidth(480)
|
||||
controlBar:SetHeight(44)
|
||||
controlBar:SetPoint("TOP", UIParent, "TOP", 0, -8)
|
||||
controlBar:SetClampedToScreen(true)
|
||||
controlBar:SetMovable(true)
|
||||
controlBar:EnableMouse(true)
|
||||
controlBar:RegisterForDrag("LeftButton")
|
||||
controlBar:SetScript("OnDragStart", function() this:StartMoving() end)
|
||||
controlBar:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||||
|
||||
controlBar:SetBackdrop(ROUND_BACKDROP)
|
||||
controlBar:SetBackdropColor(panelBg[1], panelBg[2], panelBg[3], panelBg[4] or 0.98)
|
||||
controlBar:SetBackdropBorderColor(panelBd[1], panelBd[2], panelBd[3], panelBd[4] or 0.90)
|
||||
|
||||
local title = controlBar:CreateFontString(nil, "OVERLAY")
|
||||
title:SetFont(Font(), 13, "OUTLINE")
|
||||
title:SetPoint("LEFT", controlBar, "LEFT", 14, 0)
|
||||
title:SetText("Nanami 布局")
|
||||
title:SetTextColor(titleC[1], titleC[2], titleC[3], 1)
|
||||
|
||||
local sep = controlBar:CreateTexture(nil, "ARTWORK")
|
||||
sep:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||
sep:SetWidth(1)
|
||||
sep:SetHeight(24)
|
||||
sep:SetPoint("LEFT", controlBar, "LEFT", 118, 0)
|
||||
sep:SetVertexColor(panelBd[1], panelBd[2], panelBd[3], 0.5)
|
||||
|
||||
local bx = 128
|
||||
|
||||
-- Snap toggle
|
||||
local snapBtn = MakeControlButton(controlBar, "", 76, bx, function()
|
||||
local cfg = GetLayoutCfg()
|
||||
cfg.snapEnabled = not cfg.snapEnabled
|
||||
if controlBar._updateSnap then controlBar._updateSnap() end
|
||||
end)
|
||||
controlBar.snapBtn = snapBtn
|
||||
|
||||
local function UpdateSnapBtnText()
|
||||
local cfg = GetLayoutCfg()
|
||||
local a2 = T().accent or { 1, 0.5, 0.8 }
|
||||
if cfg.snapEnabled then
|
||||
snapBtn._text:SetText("|cff66ee66开|r 磁吸")
|
||||
snapBtn:SetBackdropBorderColor(a2[1], a2[2], a2[3], 0.7)
|
||||
else
|
||||
snapBtn._text:SetText("|cffee6666关|r 磁吸")
|
||||
snapBtn:SetBackdropBorderColor(0.4, 0.2, 0.2, 0.7)
|
||||
end
|
||||
end
|
||||
controlBar._updateSnap = UpdateSnapBtnText
|
||||
|
||||
-- Grid toggle
|
||||
local gridBtn = MakeControlButton(controlBar, "", 76, bx + 84, function()
|
||||
local cfg = GetLayoutCfg()
|
||||
cfg.showGrid = not cfg.showGrid
|
||||
if controlBar._updateGrid then controlBar._updateGrid() end
|
||||
if gridFrame then
|
||||
if cfg.showGrid then gridFrame:Show() else gridFrame:Hide() end
|
||||
end
|
||||
end)
|
||||
controlBar.gridBtn = gridBtn
|
||||
|
||||
local function UpdateGridBtnText()
|
||||
local cfg = GetLayoutCfg()
|
||||
local a2 = T().accent or { 1, 0.5, 0.8 }
|
||||
if cfg.showGrid then
|
||||
gridBtn._text:SetText("|cff66ee66开|r 网格")
|
||||
gridBtn:SetBackdropBorderColor(a2[1], a2[2], a2[3], 0.7)
|
||||
else
|
||||
gridBtn._text:SetText("|cffee6666关|r 网格")
|
||||
gridBtn:SetBackdropBorderColor(0.4, 0.2, 0.2, 0.7)
|
||||
end
|
||||
end
|
||||
controlBar._updateGrid = UpdateGridBtnText
|
||||
|
||||
-- Reset all
|
||||
local resetBtn = MakeControlButton(controlBar, "全部重置", 76, bx + 176, function()
|
||||
M:ResetAllMovers()
|
||||
end)
|
||||
local wbGold = th.wbGold or { 1, 0.88, 0.55 }
|
||||
resetBtn._text:SetTextColor(wbGold[1], wbGold[2], wbGold[3], 1)
|
||||
|
||||
-- Close
|
||||
local closeBtn = CreateFrame("Button", nil, controlBar)
|
||||
closeBtn:SetWidth(60)
|
||||
closeBtn:SetHeight(26)
|
||||
closeBtn:SetPoint("RIGHT", controlBar, "RIGHT", -10, 0)
|
||||
closeBtn:SetBackdrop(ROUND_BACKDROP)
|
||||
closeBtn:SetBackdropColor(0.35, 0.08, 0.10, 0.95)
|
||||
closeBtn:SetBackdropBorderColor(0.65, 0.20, 0.25, 0.90)
|
||||
local closeText = closeBtn:CreateFontString(nil, "OVERLAY")
|
||||
closeText:SetFont(Font(), 10, "OUTLINE")
|
||||
closeText:SetPoint("CENTER", 0, 0)
|
||||
closeText:SetText("关闭")
|
||||
closeText:SetTextColor(1, 0.65, 0.65, 1)
|
||||
|
||||
closeBtn:SetScript("OnClick", function() M:ExitLayoutMode() end)
|
||||
closeBtn:SetScript("OnEnter", function()
|
||||
this:SetBackdropColor(0.50, 0.12, 0.15, 0.98)
|
||||
this:SetBackdropBorderColor(1, 0.35, 0.40, 1)
|
||||
closeText:SetTextColor(1, 1, 1, 1)
|
||||
end)
|
||||
closeBtn:SetScript("OnLeave", function()
|
||||
this:SetBackdropColor(0.35, 0.08, 0.10, 0.95)
|
||||
this:SetBackdropBorderColor(0.65, 0.20, 0.25, 0.90)
|
||||
closeText:SetTextColor(1, 0.65, 0.65, 1)
|
||||
end)
|
||||
closeBtn:SetScript("OnMouseDown", function()
|
||||
this:SetBackdropColor(0.25, 0.04, 0.06, 0.98)
|
||||
end)
|
||||
closeBtn:SetScript("OnMouseUp", function()
|
||||
this:SetBackdropColor(0.50, 0.12, 0.15, 0.98)
|
||||
end)
|
||||
controlBar.closeBtn = closeBtn
|
||||
|
||||
controlBar:Hide()
|
||||
return controlBar
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Register mover
|
||||
--------------------------------------------------------------------------------
|
||||
function M:RegisterMover(name, frame, label, defaultPoint, defaultRelativeTo, defaultRelPoint, defaultX, defaultY, onMoved)
|
||||
if not name or not frame then return end
|
||||
|
||||
registry[name] = {
|
||||
frame = frame,
|
||||
label = label or name,
|
||||
defaultPoint = defaultPoint or "CENTER",
|
||||
defaultRelativeTo = defaultRelativeTo or "UIParent",
|
||||
defaultRelPoint = defaultRelPoint or "CENTER",
|
||||
defaultX = defaultX or 0,
|
||||
defaultY = defaultY or 0,
|
||||
onMoved = onMoved,
|
||||
}
|
||||
|
||||
CreateMoverFrame(name, registry[name])
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Enter / Exit layout mode
|
||||
--------------------------------------------------------------------------------
|
||||
function M:EnterLayoutMode()
|
||||
if isLayoutMode then return end
|
||||
isLayoutMode = true
|
||||
|
||||
if SFrames.ConfigUI and SFramesConfigPanel and SFramesConfigPanel:IsShown() then
|
||||
SFramesConfigPanel:Hide()
|
||||
end
|
||||
|
||||
CreateOverlay()
|
||||
CreateGrid()
|
||||
CreateControlBar()
|
||||
|
||||
overlayFrame:Show()
|
||||
|
||||
local cfg = GetLayoutCfg()
|
||||
if cfg.showGrid then gridFrame:Show() end
|
||||
if controlBar._updateSnap then controlBar._updateSnap() end
|
||||
if controlBar._updateGrid then controlBar._updateGrid() end
|
||||
controlBar:Show()
|
||||
|
||||
SFrames:Print(string.format(
|
||||
"|cff66eeff[布局]|r UIScale=%.2f screenW=%.0f screenH=%.0f (GetRight=%.0f GetTop=%.0f)",
|
||||
UIParent:GetEffectiveScale(),
|
||||
UIParent:GetWidth(), UIParent:GetHeight(),
|
||||
UIParent:GetRight() or 0, UIParent:GetTop() or 0))
|
||||
|
||||
for name, _ in pairs(registry) do
|
||||
SyncMoverToFrame(name)
|
||||
local mover = moverFrames[name]
|
||||
if mover then mover:Show() end
|
||||
end
|
||||
|
||||
SFrames:Print("布局模式已开启 - 拖拽移动 | 箭头微调 | 右键重置 | Shift禁用磁吸")
|
||||
end
|
||||
|
||||
function M:ExitLayoutMode()
|
||||
if not isLayoutMode then return end
|
||||
isLayoutMode = false
|
||||
|
||||
for name, _ in pairs(registry) do
|
||||
SyncFrameToMover(name)
|
||||
local mover = moverFrames[name]
|
||||
if mover then mover:Hide() end
|
||||
end
|
||||
|
||||
if gridFrame then gridFrame:Hide() end
|
||||
if controlBar then controlBar:Hide() end
|
||||
if overlayFrame then overlayFrame:Hide() end
|
||||
|
||||
SFrames:Print("布局模式已关闭 - 所有位置已保存")
|
||||
end
|
||||
|
||||
function M:ToggleLayoutMode()
|
||||
if isLayoutMode then self:ExitLayoutMode() else self:EnterLayoutMode() end
|
||||
end
|
||||
|
||||
function M:IsLayoutMode()
|
||||
return isLayoutMode
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Reset movers
|
||||
--------------------------------------------------------------------------------
|
||||
function M:ResetMover(name)
|
||||
local entry = registry[name]
|
||||
if not entry then return end
|
||||
|
||||
local positions = EnsurePositions()
|
||||
positions[name] = nil
|
||||
|
||||
local frame = entry.frame
|
||||
if frame then
|
||||
frame:ClearAllPoints()
|
||||
local relTo = _G[entry.defaultRelativeTo] or UIParent
|
||||
frame:SetPoint(entry.defaultPoint, relTo, entry.defaultRelPoint,
|
||||
entry.defaultX, entry.defaultY)
|
||||
end
|
||||
|
||||
if entry.onMoved then entry.onMoved() end
|
||||
if isLayoutMode then SyncMoverToFrame(name) end
|
||||
|
||||
SFrames:Print((entry.label or name) .. " 位置已重置")
|
||||
end
|
||||
|
||||
function M:ResetAllMovers()
|
||||
for name, _ in pairs(registry) do
|
||||
local entry = registry[name]
|
||||
local positions = EnsurePositions()
|
||||
positions[name] = nil
|
||||
|
||||
local frame = entry.frame
|
||||
if frame then
|
||||
frame:ClearAllPoints()
|
||||
local relTo = _G[entry.defaultRelativeTo] or UIParent
|
||||
frame:SetPoint(entry.defaultPoint, relTo, entry.defaultRelPoint,
|
||||
entry.defaultX, entry.defaultY)
|
||||
end
|
||||
if entry.onMoved then entry.onMoved() end
|
||||
end
|
||||
|
||||
if isLayoutMode then
|
||||
for name, _ in pairs(registry) do SyncMoverToFrame(name) end
|
||||
end
|
||||
|
||||
SFrames:Print("所有位置已重置为默认")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Apply saved position to a frame (for modules to call on init)
|
||||
--------------------------------------------------------------------------------
|
||||
function M:ApplyPosition(name, frame, defaultPoint, defaultRelTo, defaultRelPoint, defaultX, defaultY)
|
||||
local positions = EnsurePositions()
|
||||
local pos = positions[name]
|
||||
if pos and pos.point and pos.relativePoint then
|
||||
frame:ClearAllPoints()
|
||||
frame:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||||
return true
|
||||
else
|
||||
frame:ClearAllPoints()
|
||||
local relFrame = (defaultRelTo and _G[defaultRelTo]) or UIParent
|
||||
frame:SetPoint(defaultPoint or "CENTER", relFrame, defaultRelPoint or "CENTER",
|
||||
defaultX or 0, defaultY or 0)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Utility
|
||||
--------------------------------------------------------------------------------
|
||||
function M:GetRegistry()
|
||||
return registry
|
||||
end
|
||||
@@ -9,6 +9,7 @@
|
||||
Bindings.xml
|
||||
Core.lua
|
||||
Config.lua
|
||||
Movers.lua
|
||||
Media.lua
|
||||
IconMap.lua
|
||||
Factory.lua
|
||||
@@ -31,12 +32,14 @@ Units\Pet.lua
|
||||
Units\Target.lua
|
||||
Units\ToT.lua
|
||||
Units\Party.lua
|
||||
TalentDefaultDB.lua
|
||||
Units\TalentTree.lua
|
||||
SellPriceDB.lua
|
||||
GearScore.lua
|
||||
Tooltip.lua
|
||||
Units\Raid.lua
|
||||
ActionBars.lua
|
||||
KeyBindManager.lua
|
||||
|
||||
Bags\Offline.lua
|
||||
Bags\Sort.lua
|
||||
@@ -47,11 +50,13 @@ Bags\Core.lua
|
||||
Merchant.lua
|
||||
Trade.lua
|
||||
Roll.lua
|
||||
LootDisplay.lua
|
||||
QuestUI.lua
|
||||
BookUI.lua
|
||||
QuestLogSkin.lua
|
||||
TrainerUI.lua
|
||||
TradeSkillDB.lua
|
||||
BeastTrainingUI.lua
|
||||
TradeSkillUI.lua
|
||||
CharacterPanel.lua
|
||||
StatSummary.lua
|
||||
|
||||
@@ -437,7 +437,7 @@ local function ApplySkin()
|
||||
end
|
||||
end
|
||||
|
||||
-- 14b) Aggressive cleanup: clear backdrops/textures on ALL non-essential children
|
||||
-- 14b) Cleanup: only target known decorative inset/background children
|
||||
local knownFrames = {}
|
||||
if PetStableModel then knownFrames[PetStableModel] = true end
|
||||
if PetStableFrameCloseButton then knownFrames[PetStableFrameCloseButton] = true end
|
||||
@@ -453,83 +453,15 @@ local function ApplySkin()
|
||||
for _, child in ipairs(allCh) do
|
||||
if not knownFrames[child] and not child.sfSkinned
|
||||
and not child.sfOverlay and not child.sfBorder then
|
||||
NukeTextures(child)
|
||||
if child.SetBackdrop then child:SetBackdrop(nil) end
|
||||
local subCh = { child:GetChildren() }
|
||||
for _, sc in ipairs(subCh) do
|
||||
NukeTextures(sc)
|
||||
if sc.SetBackdrop then sc:SetBackdrop(nil) end
|
||||
local cName = child:GetName() or ""
|
||||
if string.find(cName, "Inset") or string.find(cName, "Bg")
|
||||
or string.find(cName, "Border") or string.find(cName, "Tab") then
|
||||
NukeTextures(child)
|
||||
if child.SetBackdrop then child:SetBackdrop(nil) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 15) Auto-compact: measure left padding, then apply uniformly to right & bottom
|
||||
local frameTop = frame:GetTop()
|
||||
local frameLeft = frame:GetLeft()
|
||||
local frameRight = frame:GetRight()
|
||||
local frameBot = frame:GetBottom()
|
||||
if frameTop and frameLeft and frameRight and frameBot then
|
||||
local contentLeft = frameRight
|
||||
local contentRight = frameLeft
|
||||
local contentBot = frameTop
|
||||
local function Scan(obj)
|
||||
if not obj:IsShown() then return end
|
||||
local l = obj.GetLeft and obj:GetLeft()
|
||||
local r = obj.GetRight and obj:GetRight()
|
||||
local b = obj.GetBottom and obj:GetBottom()
|
||||
if l and l < contentLeft then contentLeft = l end
|
||||
if r and r > contentRight then contentRight = r end
|
||||
if b and b < contentBot then contentBot = b end
|
||||
end
|
||||
local children = { frame:GetChildren() }
|
||||
for _, child in ipairs(children) do
|
||||
if not child.sfOverlay and not child.sfBorder then
|
||||
Scan(child)
|
||||
end
|
||||
end
|
||||
local regions = { frame:GetRegions() }
|
||||
for _, r in ipairs(regions) do
|
||||
if not r.sfNuked and not r.sfKeep then
|
||||
Scan(r)
|
||||
end
|
||||
end
|
||||
-- Also scan named Blizzard content elements directly
|
||||
local contentNames = {
|
||||
"PetStableCurrentPet", "PetStablePurchaseButton",
|
||||
"PetStableModel", "PetStableFrameCloseButton",
|
||||
}
|
||||
for i = 1, 20 do
|
||||
table.insert(contentNames, "PetStableStableSlot" .. i)
|
||||
table.insert(contentNames, "PetStableSlot" .. i)
|
||||
end
|
||||
for _, n in ipairs(contentNames) do
|
||||
local obj = _G[n]
|
||||
if obj and obj.IsShown and obj:IsShown() then Scan(obj) end
|
||||
end
|
||||
-- Scan visible FontStrings on the frame (they are content)
|
||||
for _, r in ipairs(regions) do
|
||||
if r:IsObjectType("FontString") and r:IsShown() and r:GetText()
|
||||
and r:GetText() ~= "" then
|
||||
local l = r:GetLeft()
|
||||
local ri = r:GetRight()
|
||||
local b = r:GetBottom()
|
||||
if l and l < contentLeft then contentLeft = l end
|
||||
if ri and ri > contentRight then contentRight = ri end
|
||||
if b and b < contentBot then contentBot = b end
|
||||
end
|
||||
end
|
||||
local leftPad = contentLeft - frameLeft
|
||||
if leftPad < 4 then leftPad = 4 end
|
||||
if leftPad > 16 then leftPad = 16 end
|
||||
local newW = (contentRight - frameLeft) + leftPad
|
||||
local newH = (frameTop - contentBot) + leftPad
|
||||
if newW < frame:GetWidth() then
|
||||
frame:SetWidth(newW)
|
||||
end
|
||||
if newH < frame:GetHeight() then
|
||||
frame:SetHeight(newH)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
119
SetupWizard.lua
119
SetupWizard.lua
@@ -338,6 +338,7 @@ local function GetDefaultChoices()
|
||||
minimapShowClock = true,
|
||||
minimapShowCoords = true,
|
||||
minimapMapStyle = "auto",
|
||||
minimapMapShape = "circle",
|
||||
buffEnabled = true,
|
||||
buffIconSize = 30,
|
||||
buffIconsPerRow = 8,
|
||||
@@ -395,6 +396,7 @@ local function GetCurrentChoices()
|
||||
if db.Minimap.showClock ~= nil then c.minimapShowClock = db.Minimap.showClock end
|
||||
if db.Minimap.showCoords ~= nil then c.minimapShowCoords = db.Minimap.showCoords end
|
||||
if db.Minimap.mapStyle ~= nil then c.minimapMapStyle = db.Minimap.mapStyle end
|
||||
if db.Minimap.mapShape ~= nil then c.minimapMapShape = db.Minimap.mapShape end
|
||||
end
|
||||
if db.MinimapBuffs then
|
||||
if db.MinimapBuffs.enabled ~= nil then c.buffEnabled = db.MinimapBuffs.enabled end
|
||||
@@ -459,6 +461,7 @@ local function ApplyChoices()
|
||||
SFramesDB.Minimap.showClock = c.minimapShowClock
|
||||
SFramesDB.Minimap.showCoords = c.minimapShowCoords
|
||||
SFramesDB.Minimap.mapStyle = c.minimapMapStyle
|
||||
SFramesDB.Minimap.mapShape = c.minimapMapShape
|
||||
|
||||
if type(SFramesDB.MinimapBuffs) ~= "table" then SFramesDB.MinimapBuffs = {} end
|
||||
SFramesDB.MinimapBuffs.enabled = c.buffEnabled
|
||||
@@ -816,12 +819,122 @@ local function BuildExtras(page)
|
||||
MakeCheck(p, "显示坐标", x + 160, y,
|
||||
function() return choices.minimapShowCoords end,
|
||||
function(v) choices.minimapShowCoords = v end)
|
||||
|
||||
local function GetMode()
|
||||
local shape = choices.minimapMapShape or "circle"
|
||||
if shape == "square1" or shape == "square2" then return "square" end
|
||||
if (choices.minimapMapStyle or "auto") == "auto" then return "auto" end
|
||||
return "round"
|
||||
end
|
||||
|
||||
local circleFrame = CreateFrame("Frame", nil, p)
|
||||
circleFrame:SetPoint("TOPLEFT", p, "TOPLEFT", x, y - 50)
|
||||
circleFrame:SetWidth(CONTENT_W)
|
||||
circleFrame:SetHeight(56)
|
||||
circleFrame:Hide()
|
||||
|
||||
MakeLabel(circleFrame, "圆形边框:", 0, 0, 10, 0.78, 0.72, 0.76)
|
||||
local mapStyles = SFrames.Minimap and SFrames.Minimap.MAP_STYLES or {}
|
||||
local CS, CG = 28, 4
|
||||
local circleBtns = {}
|
||||
|
||||
local autoStyleBtn = MakeButton(circleFrame, "自动", 38, CS, nil)
|
||||
autoStyleBtn:ClearAllPoints()
|
||||
autoStyleBtn:SetPoint("TOPLEFT", circleFrame, "TOPLEFT", 0, -16)
|
||||
|
||||
local function RefreshCircleHighlight()
|
||||
local cur = choices.minimapMapStyle or "auto"
|
||||
local matched = (cur == "auto")
|
||||
for _, b in ipairs(circleBtns) do
|
||||
if b.styleKey == cur then
|
||||
b:SetBackdropBorderColor(1, 0.78, 0.2, 1)
|
||||
matched = true
|
||||
else
|
||||
b:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||||
end
|
||||
end
|
||||
if not matched then
|
||||
choices.minimapMapStyle = "auto"
|
||||
end
|
||||
autoStyleBtn.sfActive = ((choices.minimapMapStyle or "auto") == "auto")
|
||||
autoStyleBtn:RefreshVisual()
|
||||
end
|
||||
|
||||
autoStyleBtn:SetScript("OnClick", function()
|
||||
choices.minimapMapStyle = "auto"
|
||||
RefreshCircleHighlight()
|
||||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||||
end)
|
||||
|
||||
for idx, style in ipairs(mapStyles) do
|
||||
local sb = CreateFrame("Button", WN("CS"), circleFrame)
|
||||
sb:SetWidth(CS); sb:SetHeight(CS)
|
||||
sb:SetPoint("TOPLEFT", circleFrame, "TOPLEFT",
|
||||
42 + (idx - 1) * (CS + CG), -16)
|
||||
sb:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 8,
|
||||
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||||
})
|
||||
sb:SetBackdropColor(0.08, 0.08, 0.1, 0.85)
|
||||
|
||||
local ptex = sb:CreateTexture(nil, "ARTWORK")
|
||||
ptex:SetTexture(style.tex)
|
||||
ptex:SetPoint("CENTER")
|
||||
ptex:SetWidth(CS - 4); ptex:SetHeight(CS - 4)
|
||||
|
||||
sb.styleKey = style.key
|
||||
sb._sfLabel = style.label
|
||||
sb:SetScript("OnClick", function()
|
||||
choices.minimapMapStyle = this.styleKey
|
||||
RefreshCircleHighlight()
|
||||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||||
end)
|
||||
sb:SetScript("OnEnter", function()
|
||||
this:SetBackdropBorderColor(0.7, 0.7, 0.7, 1)
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
GameTooltip:SetText(this._sfLabel)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
sb:SetScript("OnLeave", function()
|
||||
local cur = choices.minimapMapStyle or "auto"
|
||||
if this.styleKey == cur then
|
||||
this:SetBackdropBorderColor(1, 0.78, 0.2, 1)
|
||||
else
|
||||
this:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||||
end
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
|
||||
table.insert(circleBtns, sb)
|
||||
end
|
||||
RefreshCircleHighlight()
|
||||
|
||||
MakeLabel(p, "地图风格:", x, y - 26, 10, 0.78, 0.72, 0.76)
|
||||
MakeBtnGroup(p, x + 60, y - 24,
|
||||
{ {key="auto", label="自动", w=50}, {key="round", label="圆形", w=50}, {key="square", label="方形", w=50} },
|
||||
function() return choices.minimapMapStyle end,
|
||||
function(v) choices.minimapMapStyle = v end)
|
||||
return 54
|
||||
GetMode,
|
||||
function(v)
|
||||
if v == "auto" then
|
||||
choices.minimapMapShape = "circle"
|
||||
choices.minimapMapStyle = "auto"
|
||||
circleFrame:Hide()
|
||||
elseif v == "round" then
|
||||
choices.minimapMapShape = "circle"
|
||||
if choices.minimapMapStyle == "auto" then
|
||||
choices.minimapMapStyle = "map"
|
||||
end
|
||||
RefreshCircleHighlight()
|
||||
circleFrame:Show()
|
||||
elseif v == "square" then
|
||||
choices.minimapMapShape = "square1"
|
||||
circleFrame:Hide()
|
||||
end
|
||||
end)
|
||||
|
||||
if GetMode() == "round" then circleFrame:Show() end
|
||||
return 110
|
||||
end, "worldmap")
|
||||
|
||||
AddFeature("Buff 栏", "自定义 Buff/Debuff 显示", "buffEnabled", function(p, x, y)
|
||||
|
||||
104
SocialUI.lua
104
SocialUI.lua
@@ -516,12 +516,24 @@ local origFriendsFrameShow
|
||||
local function HideBlizzardFriends()
|
||||
if not FriendsFrame then return end
|
||||
origFriendsFrameShow = FriendsFrame.Show
|
||||
FriendsFrame:Hide()
|
||||
FriendsFrame:SetAlpha(0)
|
||||
FriendsFrame:EnableMouse(false)
|
||||
FriendsFrame:ClearAllPoints()
|
||||
FriendsFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 2000, 2000)
|
||||
FriendsFrame:UnregisterAllEvents()
|
||||
FriendsFrame.Show = function() end
|
||||
|
||||
if UIPanelWindows then
|
||||
UIPanelWindows["FriendsFrame"] = nil
|
||||
end
|
||||
|
||||
for i = table.getn(UISpecialFrames), 1, -1 do
|
||||
if UISpecialFrames[i] == "FriendsFrame" then
|
||||
table.remove(UISpecialFrames, i)
|
||||
end
|
||||
end
|
||||
|
||||
if SetWhoToUI then
|
||||
local origSetWhoToUI = SetWhoToUI
|
||||
SetWhoToUI = function(flag)
|
||||
@@ -1248,8 +1260,82 @@ end
|
||||
--------------------------------------------------------------------------------
|
||||
-- Tab 3: Guild
|
||||
--------------------------------------------------------------------------------
|
||||
local motdPopup = nil
|
||||
local function ShowMotdPopup(motdStr)
|
||||
if not motdPopup then
|
||||
local f = CreateFrame("Frame", "SFramesSocialMotdPopup", UIParent)
|
||||
f:SetWidth(360)
|
||||
f:SetHeight(220)
|
||||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 60)
|
||||
f:SetFrameStrata("DIALOG")
|
||||
f:SetFrameLevel(120)
|
||||
SetRoundBackdrop(f, T.panelBg, T.panelBorder)
|
||||
CreateShadow(f)
|
||||
f:EnableMouse(true)
|
||||
f:SetMovable(true)
|
||||
f:RegisterForDrag("LeftButton")
|
||||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||||
|
||||
local titleFS = MakeFS(f, 12, "CENTER", T.gold)
|
||||
titleFS:SetPoint("TOP", f, "TOP", 0, -10)
|
||||
titleFS:SetText("公会公告")
|
||||
f.titleFS = titleFS
|
||||
|
||||
MakeSep(f, -26)
|
||||
|
||||
local scrollFrame = CreateFrame("ScrollFrame", NextName("MotdScroll"), f)
|
||||
scrollFrame:SetPoint("TOPLEFT", f, "TOPLEFT", 12, -30)
|
||||
scrollFrame:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -12, 40)
|
||||
|
||||
local child = CreateFrame("Frame", nil, scrollFrame)
|
||||
child:SetWidth(336 - 24)
|
||||
child:SetHeight(1)
|
||||
scrollFrame:SetScrollChild(child)
|
||||
|
||||
local bodyFS = child:CreateFontString(nil, "OVERLAY")
|
||||
bodyFS:SetFont(GetFont(), 11, "OUTLINE")
|
||||
bodyFS:SetJustifyH("LEFT")
|
||||
bodyFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
||||
bodyFS:SetPoint("TOPLEFT", child, "TOPLEFT", 0, 0)
|
||||
bodyFS:SetWidth(336 - 24)
|
||||
f.bodyFS = bodyFS
|
||||
|
||||
local scrollOffset = 0
|
||||
local scrollMax = 0
|
||||
|
||||
f.UpdateScroll = function()
|
||||
local textH = bodyFS:GetHeight() or 0
|
||||
local viewH = scrollFrame:GetHeight() or 1
|
||||
scrollMax = math.max(0, textH - viewH)
|
||||
if scrollOffset > scrollMax then scrollOffset = scrollMax end
|
||||
scrollFrame:SetVerticalScroll(scrollOffset)
|
||||
child:SetHeight(math.max(textH, viewH))
|
||||
end
|
||||
|
||||
scrollFrame:EnableMouseWheel(true)
|
||||
scrollFrame:SetScript("OnMouseWheel", function()
|
||||
scrollOffset = scrollOffset - (arg1 or 0) * 18
|
||||
if scrollOffset < 0 then scrollOffset = 0 end
|
||||
if scrollOffset > scrollMax then scrollOffset = scrollMax end
|
||||
scrollFrame:SetVerticalScroll(scrollOffset)
|
||||
end)
|
||||
|
||||
local closeBtn = MakeButton(f, "关闭", 100, 24)
|
||||
closeBtn:SetPoint("BOTTOM", f, "BOTTOM", 0, 10)
|
||||
closeBtn:SetScript("OnClick", function() f:Hide() end)
|
||||
|
||||
f:Hide()
|
||||
motdPopup = f
|
||||
end
|
||||
|
||||
motdPopup.bodyFS:SetText(motdStr or "无公告")
|
||||
motdPopup:Show()
|
||||
motdPopup.UpdateScroll()
|
||||
end
|
||||
|
||||
local function BuildGuildPage(page)
|
||||
local motdFrame = CreateFrame("Frame", nil, page)
|
||||
local motdFrame = CreateFrame("Button", NextName("MotdBtn"), page)
|
||||
motdFrame:SetHeight(36)
|
||||
motdFrame:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0)
|
||||
motdFrame:SetPoint("TOPRIGHT", page, "TOPRIGHT", 0, 0)
|
||||
@@ -1257,13 +1343,24 @@ local function BuildGuildPage(page)
|
||||
|
||||
local motdLabel = MakeFS(motdFrame, 9, "LEFT", T.dimText)
|
||||
motdLabel:SetPoint("TOPLEFT", motdFrame, "TOPLEFT", 6, -2)
|
||||
motdLabel:SetText("公会公告:")
|
||||
motdLabel:SetText("公会公告: (点击查看完整)")
|
||||
|
||||
local motdText = MakeFS(motdFrame, 10, "LEFT", T.bodyText)
|
||||
motdText:SetPoint("TOPLEFT", motdLabel, "BOTTOMLEFT", 0, -2)
|
||||
motdText:SetPoint("RIGHT", motdFrame, "RIGHT", -6, 0)
|
||||
page.motdText = motdText
|
||||
|
||||
motdFrame:SetScript("OnClick", function()
|
||||
local motd = GetGuildRosterMOTD and GetGuildRosterMOTD() or ""
|
||||
ShowMotdPopup(motd ~= "" and motd or "无公告")
|
||||
end)
|
||||
motdFrame:SetScript("OnEnter", function()
|
||||
SetPixelBackdrop(this, { 0.12, 0.06, 0.09, 0.9 }, T.tabActiveBorder)
|
||||
end)
|
||||
motdFrame:SetScript("OnLeave", function()
|
||||
SetPixelBackdrop(this, { 0.08, 0.04, 0.06, 0.8 }, T.slotBorder)
|
||||
end)
|
||||
|
||||
-- Toolbar row: search + filter buttons
|
||||
local toolBar = CreateFrame("Frame", nil, page)
|
||||
toolBar:SetHeight(18)
|
||||
@@ -2459,6 +2556,9 @@ function SUI:Initialize()
|
||||
local wasPending = whoQueryPending
|
||||
whoQueryPending = false
|
||||
if SetWhoToUI then SetWhoToUI(1) end
|
||||
if FriendsFrame and FriendsFrame:IsShown() then
|
||||
FriendsFrame:Hide()
|
||||
end
|
||||
if MainFrame and MainFrame:IsShown() and currentMainTab == 2 then
|
||||
WhoDebug("更新列表显示")
|
||||
SUI:UpdateWhoList()
|
||||
|
||||
6266
TalentDefaultDB.lua
Normal file
6266
TalentDefaultDB.lua
Normal file
File diff suppressed because it is too large
Load Diff
100
Tooltip.lua
100
Tooltip.lua
@@ -147,6 +147,11 @@ function SFrames.FloatingTooltip:Initialize()
|
||||
bg:SetAllPoints(bgFrame)
|
||||
GameTooltip._nanamiBGTex = bg
|
||||
|
||||
bgFrame:SetScript("OnUpdate", function()
|
||||
if not GameTooltip:IsVisible() then
|
||||
this:Hide()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
@@ -191,16 +196,27 @@ function SFrames.FloatingTooltip:Initialize()
|
||||
GameTooltipStatusBar.SetStatusBarColor = function() return end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- Flag: true when tooltip was positioned via GameTooltip_SetDefaultAnchor
|
||||
-- (world mouseover units). False for bag/bank/inventory item tooltips
|
||||
-- that have their own anchoring — those should NOT be cursor-followed.
|
||||
--------------------------------------------------------------------------
|
||||
local ttUsesDefaultAnchor = false
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- Track mouseover name/level for health estimation
|
||||
--------------------------------------------------------------------------
|
||||
local ttMouseName, ttMouseLevel
|
||||
local ttHadUnit = false
|
||||
|
||||
local barEvents = CreateFrame("Frame", nil, GameTooltipStatusBar)
|
||||
barEvents:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
|
||||
barEvents:SetScript("OnEvent", function()
|
||||
ttMouseName = UnitName("mouseover")
|
||||
ttMouseLevel = UnitLevel("mouseover")
|
||||
if UnitExists("mouseover") then
|
||||
ttHadUnit = true
|
||||
end
|
||||
end)
|
||||
|
||||
local function TT_Abbreviate(val)
|
||||
@@ -242,6 +258,7 @@ function SFrames.FloatingTooltip:Initialize()
|
||||
local orig_SetOwner = GameTooltip.SetOwner
|
||||
GameTooltip.SetOwner = function(self, owner, anchor, xOff, yOff)
|
||||
ttOwner = owner
|
||||
ttUsesDefaultAnchor = false
|
||||
return orig_SetOwner(self, owner, anchor, xOff, yOff)
|
||||
end
|
||||
|
||||
@@ -312,43 +329,64 @@ function SFrames.FloatingTooltip:Initialize()
|
||||
end
|
||||
end)
|
||||
|
||||
-- OnUpdate: throttled backdrop/bar refresh and cursor tracking
|
||||
-- OnUpdate: line formatting (once) + cursor tracking (every frame)
|
||||
local orig_OnUpdate = GameTooltip:GetScript("OnUpdate")
|
||||
local ttThrottle = 0
|
||||
local ttFormatThrottle = 0
|
||||
GameTooltip:SetScript("OnUpdate", function()
|
||||
if orig_OnUpdate then orig_OnUpdate() end
|
||||
|
||||
ttThrottle = ttThrottle + arg1
|
||||
if ttThrottle < 0.05 then return end
|
||||
ttThrottle = 0
|
||||
local isCursorMode = ttUsesDefaultAnchor and SFramesDB
|
||||
and SFramesDB.tooltipMode == "CURSOR" and not ttIsMapMarker
|
||||
|
||||
TT_ApplyBackdrop(this)
|
||||
TT_SyncBGFrame()
|
||||
if not ttIsMapMarker then
|
||||
TT_ShowBar(UnitExists("mouseover"))
|
||||
if isCursorMode then
|
||||
local hasUnit = UnitExists("mouseover")
|
||||
|
||||
if ttHadUnit and not hasUnit then
|
||||
TT_ShowBar(false)
|
||||
if GameTooltip._nanamiBGFrame then
|
||||
GameTooltip._nanamiBGFrame:Hide()
|
||||
end
|
||||
this:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
if not hasUnit then
|
||||
TT_ShowBar(false)
|
||||
end
|
||||
|
||||
local x, y = GetCursorPosition()
|
||||
local uiScale = UIParent:GetEffectiveScale()
|
||||
local ttScale = this:GetScale() or 1
|
||||
if uiScale and uiScale > 0 and ttScale > 0 then
|
||||
local effScale = uiScale * ttScale
|
||||
local tx = (x / effScale) + 16
|
||||
local ty = (y / effScale) - 16
|
||||
this:ClearAllPoints()
|
||||
this:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", tx, ty)
|
||||
TT_SyncBGFrame()
|
||||
end
|
||||
end
|
||||
|
||||
ttFormatThrottle = ttFormatThrottle + arg1
|
||||
if ttFormatThrottle < 0.05 then return end
|
||||
ttFormatThrottle = 0
|
||||
|
||||
if not linesFormatted then
|
||||
linesFormatted = true
|
||||
if not ttIsMapMarker and UnitExists("mouseover") then
|
||||
SFrames.FloatingTooltip:FormatLines(this)
|
||||
end
|
||||
end
|
||||
|
||||
if SFramesDB and SFramesDB.tooltipMode == "CURSOR" and not ttIsMapMarker then
|
||||
local x, y = GetCursorPosition()
|
||||
local scale = UIParent:GetEffectiveScale()
|
||||
if scale and scale > 0 then
|
||||
this:ClearAllPoints()
|
||||
this:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", (x / scale) + 16, (y / scale) - 16)
|
||||
end
|
||||
TT_SyncBGFrame()
|
||||
end
|
||||
end)
|
||||
|
||||
-- OnHide: hide bg frame, reset flag and owner tracking
|
||||
-- OnHide: hide bg frame, health bar, reset flags and owner tracking
|
||||
local orig_OnHide = GameTooltip:GetScript("OnHide")
|
||||
GameTooltip:SetScript("OnHide", function()
|
||||
linesFormatted = false
|
||||
ttOwner = nil
|
||||
ttHadUnit = false
|
||||
TT_ShowBar(false)
|
||||
if GameTooltip._nanamiBGFrame then
|
||||
GameTooltip._nanamiBGFrame:Hide()
|
||||
end
|
||||
@@ -416,9 +454,11 @@ function SFrames.FloatingTooltip:Initialize()
|
||||
else
|
||||
orig_GameTooltip_SetDefaultAnchor(tooltip, parent)
|
||||
end
|
||||
ttUsesDefaultAnchor = true
|
||||
end
|
||||
end
|
||||
SFrames.FloatingTooltip:ApplyConfig()
|
||||
SFrames.FloatingTooltip:ApplyScale()
|
||||
|
||||
-- WorldMapTooltip: raw textures on a child frame (SetBackdrop is unreliable)
|
||||
if WorldMapTooltip and not WorldMapTooltip._nanamiBG then
|
||||
@@ -451,15 +491,35 @@ function SFrames.FloatingTooltip:Initialize()
|
||||
if SFrames.ItemCompare and SFrames.ItemCompare.HookTooltips then
|
||||
SFrames.ItemCompare:HookTooltips()
|
||||
end
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover and self.anchor then
|
||||
SFrames.Movers:RegisterMover("Tooltip", self.anchor, "提示框",
|
||||
"BOTTOMRIGHT", "UIParent", "BOTTOMRIGHT", -20, 100)
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.FloatingTooltip:ApplyConfig()
|
||||
if SFramesDB and SFramesDB.tooltipX and SFramesDB.tooltipY and self.anchor then
|
||||
if not self.anchor then return end
|
||||
local pos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["Tooltip"]
|
||||
if pos and pos.point and pos.relativePoint then
|
||||
self.anchor:ClearAllPoints()
|
||||
self.anchor:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||||
elseif SFramesDB and SFramesDB.tooltipX and SFramesDB.tooltipY then
|
||||
self.anchor:ClearAllPoints()
|
||||
self.anchor:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", SFramesDB.tooltipX, SFramesDB.tooltipY)
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.FloatingTooltip:ApplyScale()
|
||||
local scale = SFramesDB and SFramesDB.tooltipScale or 1.0
|
||||
if scale < 0.5 then scale = 0.5 end
|
||||
if scale > 2.0 then scale = 2.0 end
|
||||
GameTooltip:SetScale(scale)
|
||||
if GameTooltip._nanamiBGFrame then
|
||||
GameTooltip._nanamiBGFrame:SetScale(scale)
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.FloatingTooltip:ToggleAnchor(show)
|
||||
if not self.anchor then return end
|
||||
if show then
|
||||
|
||||
@@ -81,7 +81,7 @@ local PROF_SPELLS = {
|
||||
["急救"]=true,["First Aid"]=true,
|
||||
["熔炼"]=true,["Smelting"]=true,
|
||||
["毒药"]=true,["Poisons"]=true,
|
||||
["训练野兽"]=true,["Beast Training"]=true,
|
||||
["生存"]=true,["Survival"]=true,
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -1129,7 +1129,12 @@ function TSUI.UpdateList()
|
||||
row.selGlow:Show()
|
||||
row.selTop:Show()
|
||||
row.selBot:Show()
|
||||
row.nameFS:SetTextColor(1, 1, 1)
|
||||
local dc = T.DIFFICULTY[entry.data.difficulty] or T.DIFFICULTY.trivial
|
||||
row.nameFS:SetTextColor(
|
||||
math.min(1, dc[1] + 0.3),
|
||||
math.min(1, dc[2] + 0.3),
|
||||
math.min(1, dc[3] + 0.3)
|
||||
)
|
||||
else
|
||||
row.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
||||
row.iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
|
||||
@@ -2056,6 +2061,8 @@ function TSUI:Initialize()
|
||||
S.MainFrame:Hide()
|
||||
end
|
||||
elseif event == "CRAFT_SHOW" then
|
||||
local craftName = GetCraftName and GetCraftName() or ""
|
||||
if craftName == "训练野兽" or craftName == "Beast Training" then return end
|
||||
S.switchStartTime = nil
|
||||
S.currentMode = "craft"
|
||||
if CraftFrame then
|
||||
|
||||
@@ -1181,6 +1181,12 @@ function TUI:Initialize()
|
||||
ClassTrainerFrame:EnableMouse(false)
|
||||
end
|
||||
|
||||
if SetTrainerServiceTypeFilter then
|
||||
SetTrainerServiceTypeFilter("available", 1)
|
||||
SetTrainerServiceTypeFilter("unavailable", 1)
|
||||
SetTrainerServiceTypeFilter("used", 1)
|
||||
end
|
||||
|
||||
selectedIndex = nil
|
||||
currentFilter = "all"
|
||||
collapsedCats = {}
|
||||
|
||||
@@ -83,6 +83,8 @@ local function InitAutoDismount()
|
||||
|
||||
local scanner = CreateFrame("GameTooltip", "NanamiDismountScan", nil, "GameTooltipTemplate")
|
||||
scanner:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||
scanner:SetAlpha(0)
|
||||
scanner:Hide()
|
||||
|
||||
local mountStrings = {
|
||||
"^Increases speed by (.+)%%",
|
||||
|
||||
@@ -582,6 +582,11 @@ function SFrames.Party:Initialize()
|
||||
end)
|
||||
|
||||
self:UpdateAll()
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover and self.parent then
|
||||
SFrames.Movers:RegisterMover("PartyFrame", self.parent, "小队",
|
||||
"TOPLEFT", "UIParent", "TOPLEFT", 15, -150)
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.Party:CreateAuras(index)
|
||||
@@ -1042,6 +1047,7 @@ function SFrames.Party:UpdateAuras(unit)
|
||||
SFrames.Tooltip:ClearLines()
|
||||
SFrames.Tooltip:SetUnitDebuff(unit, i)
|
||||
local timeLeft = SFrames:GetAuraTimeLeft(unit, i, false)
|
||||
SFrames.Tooltip:Hide()
|
||||
if timeLeft and timeLeft > 0 then
|
||||
local newExp = GetTime() + timeLeft
|
||||
if not b.expirationTime or math.abs(b.expirationTime - newExp) > 2 then
|
||||
@@ -1093,6 +1099,7 @@ function SFrames.Party:UpdateAuras(unit)
|
||||
SFrames.Tooltip:ClearLines()
|
||||
SFrames.Tooltip:SetUnitBuff(unit, i)
|
||||
local timeLeft = SFrames:GetAuraTimeLeft(unit, i, true)
|
||||
SFrames.Tooltip:Hide()
|
||||
if timeLeft and timeLeft > 0 then
|
||||
local newExp = GetTime() + timeLeft
|
||||
if not b.expirationTime or math.abs(b.expirationTime - newExp) > 2 then
|
||||
|
||||
232
Units/Pet.lua
232
Units/Pet.lua
@@ -7,6 +7,220 @@ local function Clamp(value, minValue, maxValue)
|
||||
return value
|
||||
end
|
||||
|
||||
function SFrames.Pet:ShowContextMenu()
|
||||
if not self.contextMenu then
|
||||
self.contextMenu = CreateFrame("Frame", "SFramesPetContextDD", UIParent, "UIDropDownMenuTemplate")
|
||||
end
|
||||
UIDropDownMenu_Initialize(self.contextMenu, function()
|
||||
local info
|
||||
|
||||
info = {}
|
||||
info.text = "查看属性"
|
||||
info.notCheckable = 1
|
||||
info.func = function()
|
||||
ToggleCharacter("PetPaperDollFrame")
|
||||
end
|
||||
UIDropDownMenu_AddButton(info)
|
||||
|
||||
local hasPetUI, isHunterPet = HasPetUI()
|
||||
if isHunterPet then
|
||||
info = {}
|
||||
info.text = "重命名"
|
||||
info.notCheckable = 1
|
||||
info.func = function() SFrames.Pet:ShowRenameDialog() end
|
||||
UIDropDownMenu_AddButton(info)
|
||||
|
||||
info = {}
|
||||
info.text = "解散宠物"
|
||||
info.notCheckable = 1
|
||||
info.func = function() if PetDismiss then PetDismiss() end end
|
||||
UIDropDownMenu_AddButton(info)
|
||||
|
||||
info = {}
|
||||
info.text = "放弃宠物"
|
||||
info.notCheckable = 1
|
||||
info.textR = 1; info.textG = 0.3; info.textB = 0.3
|
||||
info.func = function() if PetAbandon then PetAbandon() end end
|
||||
UIDropDownMenu_AddButton(info)
|
||||
else
|
||||
info = {}
|
||||
info.text = "解散宠物"
|
||||
info.notCheckable = 1
|
||||
info.func = function() if PetDismiss then PetDismiss() end end
|
||||
UIDropDownMenu_AddButton(info)
|
||||
end
|
||||
|
||||
info = {}
|
||||
info.text = CANCEL or "取消"
|
||||
info.notCheckable = 1
|
||||
info.func = function() CloseDropDownMenus() end
|
||||
UIDropDownMenu_AddButton(info)
|
||||
end, "MENU")
|
||||
ToggleDropDownMenu(1, nil, self.contextMenu, "SFramesPetFrame", 106, 27)
|
||||
end
|
||||
|
||||
function SFrames.Pet:CreateRenameFrame()
|
||||
local T = SFrames.ActiveTheme
|
||||
local font = SFrames:GetFont()
|
||||
local outline = (SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
|
||||
|
||||
local f = CreateFrame("Frame", "SFramesPetRenameDialog", UIParent)
|
||||
f:SetWidth(300)
|
||||
f:SetHeight(120)
|
||||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 80)
|
||||
f:SetFrameStrata("DIALOG")
|
||||
f:SetToplevel(true)
|
||||
f:EnableMouse(true)
|
||||
f:SetMovable(true)
|
||||
f:RegisterForDrag("LeftButton")
|
||||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||||
|
||||
f:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 14,
|
||||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||||
})
|
||||
f:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4])
|
||||
f:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])
|
||||
|
||||
local shadow = CreateFrame("Frame", nil, f)
|
||||
shadow:SetPoint("TOPLEFT", f, "TOPLEFT", -4, 4)
|
||||
shadow:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 4, -4)
|
||||
shadow:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 16,
|
||||
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
||||
})
|
||||
shadow:SetBackdropColor(0, 0, 0, 0.45)
|
||||
shadow:SetBackdropBorderColor(0, 0, 0, 0.6)
|
||||
shadow:SetFrameLevel(math.max(0, f:GetFrameLevel() - 1))
|
||||
|
||||
local header = CreateFrame("Frame", nil, f)
|
||||
header:SetPoint("TOPLEFT", f, "TOPLEFT", 3, -3)
|
||||
header:SetPoint("TOPRIGHT", f, "TOPRIGHT", -3, -3)
|
||||
header:SetHeight(26)
|
||||
header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
|
||||
header:SetBackdropColor(T.headerBg[1], T.headerBg[2], T.headerBg[3], T.headerBg[4])
|
||||
|
||||
local titleFS = header:CreateFontString(nil, "OVERLAY")
|
||||
titleFS:SetFont(font, 12, outline)
|
||||
titleFS:SetPoint("CENTER", header, "CENTER", 0, 0)
|
||||
titleFS:SetText("宠物重命名")
|
||||
titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
||||
|
||||
local hsep = f:CreateTexture(nil, "ARTWORK")
|
||||
hsep:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
hsep:SetHeight(1)
|
||||
hsep:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -29)
|
||||
hsep:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -29)
|
||||
hsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
||||
|
||||
local eb = CreateFrame("EditBox", "SFramesPetRenameEditBox", f)
|
||||
eb:SetWidth(260)
|
||||
eb:SetHeight(24)
|
||||
eb:SetPoint("TOP", f, "TOP", 0, -42)
|
||||
eb:SetFont(font, 12, outline)
|
||||
eb:SetAutoFocus(false)
|
||||
eb:SetMaxLetters(24)
|
||||
eb:SetBackdrop({
|
||||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||||
tile = false, tileSize = 0, edgeSize = 1,
|
||||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||||
})
|
||||
eb:SetBackdropColor(T.inputBg[1], T.inputBg[2], T.inputBg[3], T.inputBg[4])
|
||||
eb:SetBackdropBorderColor(T.inputBorder[1], T.inputBorder[2], T.inputBorder[3], T.inputBorder[4])
|
||||
eb:SetTextInsets(8, 8, 0, 0)
|
||||
eb:SetTextColor(1, 1, 1)
|
||||
|
||||
eb:SetScript("OnEnterPressed", function()
|
||||
SFrames.Pet:DoRename(this:GetText())
|
||||
end)
|
||||
eb:SetScript("OnEscapePressed", function()
|
||||
SFrames.Pet.renameFrame:Hide()
|
||||
end)
|
||||
eb:SetScript("OnEditFocusGained", function()
|
||||
this:SetBackdropBorderColor(T.accent[1], T.accent[2], T.accent[3], 1)
|
||||
end)
|
||||
eb:SetScript("OnEditFocusLost", function()
|
||||
this:SetBackdropBorderColor(T.inputBorder[1], T.inputBorder[2], T.inputBorder[3], T.inputBorder[4])
|
||||
end)
|
||||
|
||||
f.editBox = eb
|
||||
|
||||
local function CreateBtn(text, parent)
|
||||
local btn = CreateFrame("Button", nil, parent)
|
||||
btn:SetWidth(120)
|
||||
btn:SetHeight(26)
|
||||
btn:SetBackdrop({
|
||||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||||
tile = false, tileSize = 0, edgeSize = 1,
|
||||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||||
})
|
||||
btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
||||
btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
|
||||
local fs = btn:CreateFontString(nil, "OVERLAY")
|
||||
fs:SetFont(font, 11, outline)
|
||||
fs:SetPoint("CENTER", 0, 0)
|
||||
fs:SetText(text)
|
||||
fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
||||
btn.label = fs
|
||||
btn:SetScript("OnEnter", function()
|
||||
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
||||
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
|
||||
this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
|
||||
end)
|
||||
btn:SetScript("OnLeave", function()
|
||||
this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
||||
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
|
||||
this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
||||
end)
|
||||
return btn
|
||||
end
|
||||
|
||||
local confirmBtn = CreateBtn("确定", f)
|
||||
confirmBtn:SetPoint("BOTTOMRIGHT", f, "BOTTOM", -4, 10)
|
||||
confirmBtn:SetScript("OnClick", function()
|
||||
SFrames.Pet:DoRename(f.editBox:GetText())
|
||||
end)
|
||||
|
||||
local cancelBtn = CreateBtn("取消", f)
|
||||
cancelBtn:SetPoint("BOTTOMLEFT", f, "BOTTOM", 4, 10)
|
||||
cancelBtn:SetScript("OnClick", function()
|
||||
f:Hide()
|
||||
end)
|
||||
|
||||
f:Hide()
|
||||
table.insert(UISpecialFrames, "SFramesPetRenameDialog")
|
||||
self.renameFrame = f
|
||||
end
|
||||
|
||||
function SFrames.Pet:ShowRenameDialog()
|
||||
if not UnitExists("pet") then return end
|
||||
if not self.renameFrame then
|
||||
self:CreateRenameFrame()
|
||||
end
|
||||
local currentName = UnitName("pet") or ""
|
||||
self.renameFrame.editBox:SetText(currentName)
|
||||
self.renameFrame:Show()
|
||||
self.renameFrame.editBox:SetFocus()
|
||||
self.renameFrame.editBox:HighlightText()
|
||||
end
|
||||
|
||||
function SFrames.Pet:DoRename(name)
|
||||
if not name or name == "" then return end
|
||||
if PetRename then
|
||||
PetRename(name)
|
||||
end
|
||||
if self.renameFrame then
|
||||
self.renameFrame:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.Pet:Initialize()
|
||||
local f = CreateFrame("Button", "SFramesPetFrame", UIParent)
|
||||
f:SetWidth(150)
|
||||
@@ -45,7 +259,7 @@ function SFrames.Pet:Initialize()
|
||||
TargetUnit("pet")
|
||||
end
|
||||
else
|
||||
ToggleDropDownMenu(1, nil, PetFrameDropDown, "SFramesPetFrame", 106, 27)
|
||||
SFrames.Pet:ShowContextMenu()
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -158,6 +372,22 @@ function SFrames.Pet:Initialize()
|
||||
|
||||
self:InitFoodFeature()
|
||||
self:UpdateAll()
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover and self.frame then
|
||||
SFrames.Movers:RegisterMover("PetFrame", self.frame, "宠物",
|
||||
"TOPLEFT", "SFramesPlayerFrame", "BOTTOMLEFT", 10, -55)
|
||||
end
|
||||
|
||||
if StaticPopup_Show then
|
||||
local origStaticPopupShow = StaticPopup_Show
|
||||
StaticPopup_Show = function(which, a1, a2, a3)
|
||||
if which == "RENAME_PET" then
|
||||
SFrames.Pet:ShowRenameDialog()
|
||||
return
|
||||
end
|
||||
return origStaticPopupShow(which, a1, a2, a3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.Pet:UpdateAll()
|
||||
|
||||
101
Units/Player.lua
101
Units/Player.lua
@@ -105,27 +105,52 @@ function SFrames.Player:ApplyConfig()
|
||||
|
||||
local cfg = self:GetConfig()
|
||||
local f = self.frame
|
||||
local db = SFramesDB or {}
|
||||
|
||||
local showPortrait = db.playerShowPortrait ~= false
|
||||
local frameAlpha = tonumber(db.playerFrameAlpha) or 1
|
||||
frameAlpha = Clamp(frameAlpha, 0.1, 1.0)
|
||||
|
||||
f:SetScale(cfg.scale)
|
||||
f:SetWidth(cfg.width)
|
||||
f:SetHeight(cfg.height)
|
||||
f:SetAlpha(frameAlpha)
|
||||
|
||||
if f.portrait then
|
||||
f.portrait:SetWidth(cfg.portraitWidth)
|
||||
f.portrait:SetHeight(cfg.height - 2)
|
||||
end
|
||||
|
||||
if f.portraitBG then
|
||||
f.portraitBG:ClearAllPoints()
|
||||
f.portraitBG:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
|
||||
f.portraitBG:SetPoint("BOTTOMRIGHT", f.portrait, "BOTTOMRIGHT", 1, -1)
|
||||
end
|
||||
|
||||
if f.health then
|
||||
f.health:ClearAllPoints()
|
||||
f.health:SetPoint("TOPLEFT", f.portrait, "TOPRIGHT", 1, 0)
|
||||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||||
f.health:SetHeight(cfg.healthHeight)
|
||||
if showPortrait then
|
||||
if f.portrait then
|
||||
f.portrait:SetWidth(cfg.portraitWidth)
|
||||
f.portrait:SetHeight(cfg.height - 2)
|
||||
f.portrait:Show()
|
||||
end
|
||||
if f.portraitBG then
|
||||
f.portraitBG:ClearAllPoints()
|
||||
f.portraitBG:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
|
||||
f.portraitBG:SetPoint("BOTTOMRIGHT", f.portrait, "BOTTOMRIGHT", 1, -1)
|
||||
f.portraitBG:Show()
|
||||
end
|
||||
if f.health then
|
||||
f.health:ClearAllPoints()
|
||||
f.health:SetPoint("TOPLEFT", f.portrait, "TOPRIGHT", 1, 0)
|
||||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||||
f.health:SetHeight(cfg.healthHeight)
|
||||
end
|
||||
if f.classIcon and f.classIcon.overlay then
|
||||
f.classIcon.overlay:ClearAllPoints()
|
||||
f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0)
|
||||
end
|
||||
else
|
||||
if f.portrait then f.portrait:Hide() end
|
||||
if f.portraitBG then f.portraitBG:Hide() end
|
||||
if f.health then
|
||||
f.health:ClearAllPoints()
|
||||
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
||||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||||
f.health:SetHeight(cfg.healthHeight)
|
||||
end
|
||||
if f.classIcon and f.classIcon.overlay then
|
||||
f.classIcon.overlay:ClearAllPoints()
|
||||
f.classIcon.overlay:SetPoint("CENTER", f, "TOPLEFT", 8, 0)
|
||||
end
|
||||
end
|
||||
|
||||
if f.healthBGFrame then
|
||||
@@ -147,6 +172,21 @@ function SFrames.Player:ApplyConfig()
|
||||
f.powerBGFrame:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1)
|
||||
end
|
||||
|
||||
if f.restOverlay then
|
||||
if showPortrait then f.restOverlay:SetAlpha(1) else f.restOverlay:SetAlpha(0) end
|
||||
end
|
||||
|
||||
if f.castbar then
|
||||
f.castbar:ClearAllPoints()
|
||||
if showPortrait then
|
||||
f.castbar:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", 0, 6)
|
||||
f.castbar:SetPoint("BOTTOMLEFT", f.portrait, "TOPLEFT", SFrames.Config.castbarHeight + 6, 6)
|
||||
else
|
||||
f.castbar:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", 0, 6)
|
||||
f.castbar:SetPoint("BOTTOMLEFT", f, "TOPLEFT", SFrames.Config.castbarHeight + 6, 6)
|
||||
end
|
||||
end
|
||||
|
||||
local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
|
||||
local fontPath = SFrames:GetFont()
|
||||
|
||||
@@ -471,7 +511,13 @@ function SFrames.Player:Initialize()
|
||||
SFrames:RegisterEvent("PARTY_MEMBERS_CHANGED", function() self:UpdateLeaderIcon() end)
|
||||
SFrames:RegisterEvent("PARTY_LEADER_CHANGED", function() self:UpdateLeaderIcon() end)
|
||||
SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcon() end)
|
||||
SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function() if arg1 == "player" then self.frame.portrait:SetUnit("player") self.frame.portrait:SetCamera(0) self.frame.portrait:SetPosition(-1.0, 0, 0) end end)
|
||||
SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function()
|
||||
if arg1 == "player" and self.frame.portrait and not (SFramesDB and SFramesDB.playerShowPortrait == false) then
|
||||
self.frame.portrait:SetUnit("player")
|
||||
self.frame.portrait:SetCamera(0)
|
||||
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
||||
end
|
||||
end)
|
||||
SFrames:RegisterEvent("UNIT_DISPLAYPOWER", function() if arg1 == "player" then self:UpdatePowerType(); self:UpdatePower() end end)
|
||||
SFrames:RegisterEvent("UPDATE_SHAPESHIFT_FORM", function() self:UpdatePowerType(); self:UpdatePower() end)
|
||||
SFrames:RegisterEvent("PLAYER_UPDATE_RESTING", function() self:UpdateRestingStatus() end)
|
||||
@@ -587,6 +633,12 @@ function SFrames.Player:ScanTrainer()
|
||||
return
|
||||
end
|
||||
|
||||
if SetTrainerServiceTypeFilter then
|
||||
SetTrainerServiceTypeFilter("available", 1)
|
||||
SetTrainerServiceTypeFilter("unavailable", 1)
|
||||
SetTrainerServiceTypeFilter("used", 1)
|
||||
end
|
||||
|
||||
local _, classEn = UnitClass("player")
|
||||
if not classEn or not SFramesDB then self.scanningTrainer = nil return end
|
||||
|
||||
@@ -966,9 +1018,12 @@ function SFrames.Player:UpdateAll()
|
||||
formattedLevel = formattedLevel .. " "
|
||||
end
|
||||
|
||||
self.frame.portrait:SetUnit("player")
|
||||
self.frame.portrait:SetCamera(0)
|
||||
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
||||
local showPortrait = not (SFramesDB and SFramesDB.playerShowPortrait == false)
|
||||
if showPortrait and self.frame.portrait then
|
||||
self.frame.portrait:SetUnit("player")
|
||||
self.frame.portrait:SetCamera(0)
|
||||
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
||||
end
|
||||
|
||||
-- Class Color for Health
|
||||
local localizedClass, class = UnitClass("player")
|
||||
@@ -1477,6 +1532,12 @@ function SFrames.Player:Initialize()
|
||||
CastingBarFrame:UnregisterAllEvents()
|
||||
CastingBarFrame:Hide()
|
||||
end
|
||||
|
||||
-- Register mover
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover and self.frame then
|
||||
SFrames.Movers:RegisterMover("PlayerFrame", self.frame, "玩家",
|
||||
"CENTER", "UIParent", "CENTER", -200, -100)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -259,6 +259,11 @@ function SFrames.Raid:Initialize()
|
||||
SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcons() end)
|
||||
|
||||
self:UpdateAll()
|
||||
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover and self.parent then
|
||||
SFrames.Movers:RegisterMover("RaidFrame", self.parent, "团队",
|
||||
"TOPLEFT", "UIParent", "TOPLEFT", 15, -200)
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.Raid:EnsureFrames()
|
||||
@@ -1003,6 +1008,7 @@ function SFrames.Raid:UpdateAuras(unit)
|
||||
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
|
||||
SFrames.Tooltip:SetUnitBuff(unit, i)
|
||||
local buffName = SFramesScanTooltipTextLeft1:GetText()
|
||||
SFrames.Tooltip:Hide()
|
||||
|
||||
if buffName then
|
||||
for pos, listData in pairs(buffsNeeded) do
|
||||
@@ -1039,6 +1045,7 @@ function SFrames.Raid:UpdateAuras(unit)
|
||||
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
|
||||
SFrames.Tooltip:SetUnitDebuff(unit, i)
|
||||
local debuffName = SFramesScanTooltipTextLeft1:GetText()
|
||||
SFrames.Tooltip:Hide()
|
||||
|
||||
if debuffName then
|
||||
for pos, listData in pairs(buffsNeeded) do
|
||||
|
||||
1306
Units/TalentTree.lua
1306
Units/TalentTree.lua
File diff suppressed because it is too large
Load Diff
162
Units/Target.lua
162
Units/Target.lua
@@ -11,6 +11,10 @@ local function Clamp(value, minValue, maxValue)
|
||||
return value
|
||||
end
|
||||
|
||||
local DIST_BASE_WIDTH = 80
|
||||
local DIST_BASE_HEIGHT = 24
|
||||
local DIST_BASE_FONTSIZE = 14
|
||||
|
||||
function SFrames.Target:GetDistance(unit)
|
||||
if not UnitExists(unit) then return nil end
|
||||
if UnitIsUnit(unit, "player") then return "0 码" end
|
||||
@@ -69,27 +73,60 @@ function SFrames.Target:ApplyConfig()
|
||||
|
||||
local cfg = self:GetConfig()
|
||||
local f = self.frame
|
||||
local db = SFramesDB or {}
|
||||
|
||||
local showPortrait = db.targetShowPortrait ~= false
|
||||
local frameAlpha = tonumber(db.targetFrameAlpha) or 1
|
||||
frameAlpha = Clamp(frameAlpha, 0.1, 1.0)
|
||||
|
||||
f:SetScale(cfg.scale)
|
||||
f:SetWidth(cfg.width)
|
||||
f:SetHeight(cfg.height)
|
||||
f:SetAlpha(frameAlpha)
|
||||
|
||||
if f.portrait then
|
||||
f.portrait:SetWidth(cfg.portraitWidth)
|
||||
f.portrait:SetHeight(cfg.height - 2)
|
||||
end
|
||||
|
||||
if f.portraitBG then
|
||||
f.portraitBG:ClearAllPoints()
|
||||
f.portraitBG:SetPoint("TOPLEFT", f.portrait, "TOPLEFT", -1, 0)
|
||||
f.portraitBG:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
|
||||
end
|
||||
|
||||
if f.health then
|
||||
f.health:ClearAllPoints()
|
||||
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
||||
f.health:SetPoint("TOPRIGHT", f.portrait, "TOPLEFT", -1, 0)
|
||||
f.health:SetHeight(cfg.healthHeight)
|
||||
if showPortrait then
|
||||
if f.portrait then
|
||||
f.portrait:SetWidth(cfg.portraitWidth)
|
||||
f.portrait:SetHeight(cfg.height - 2)
|
||||
f.portrait:Show()
|
||||
end
|
||||
if f.portraitBG then
|
||||
f.portraitBG:ClearAllPoints()
|
||||
f.portraitBG:SetPoint("TOPLEFT", f.portrait, "TOPLEFT", -1, 0)
|
||||
f.portraitBG:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
|
||||
f.portraitBG:Show()
|
||||
end
|
||||
if f.health then
|
||||
f.health:ClearAllPoints()
|
||||
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
||||
f.health:SetPoint("TOPRIGHT", f.portrait, "TOPLEFT", -1, 0)
|
||||
f.health:SetHeight(cfg.healthHeight)
|
||||
end
|
||||
if f.classIcon and f.classIcon.overlay then
|
||||
f.classIcon.overlay:ClearAllPoints()
|
||||
f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0)
|
||||
end
|
||||
if f.comboText then
|
||||
f.comboText:ClearAllPoints()
|
||||
f.comboText:SetPoint("CENTER", f.portrait, "CENTER", 0, 0)
|
||||
end
|
||||
else
|
||||
if f.portrait then f.portrait:Hide() end
|
||||
if f.portraitBG then f.portraitBG:Hide() end
|
||||
if f.health then
|
||||
f.health:ClearAllPoints()
|
||||
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
||||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||||
f.health:SetHeight(cfg.healthHeight)
|
||||
end
|
||||
if f.classIcon and f.classIcon.overlay then
|
||||
f.classIcon.overlay:ClearAllPoints()
|
||||
f.classIcon.overlay:SetPoint("CENTER", f, "TOPRIGHT", -8, 0)
|
||||
end
|
||||
if f.comboText then
|
||||
f.comboText:ClearAllPoints()
|
||||
f.comboText:SetPoint("RIGHT", f.health, "RIGHT", -4, 0)
|
||||
end
|
||||
end
|
||||
|
||||
if f.healthBGFrame then
|
||||
@@ -124,9 +161,20 @@ function SFrames.Target:ApplyConfig()
|
||||
f.powerText:SetFont(fontPath, cfg.valueFont, outline)
|
||||
end
|
||||
|
||||
if f.castbar then
|
||||
f.castbar:ClearAllPoints()
|
||||
if showPortrait then
|
||||
f.castbar:SetPoint("BOTTOMLEFT", f, "TOPLEFT", 0, 6)
|
||||
f.castbar:SetPoint("BOTTOMRIGHT", f.portrait, "TOPRIGHT", -(SFrames.Config.castbarHeight + 6), 6)
|
||||
else
|
||||
f.castbar:SetPoint("BOTTOMLEFT", f, "TOPLEFT", 0, 6)
|
||||
f.castbar:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", -(SFrames.Config.castbarHeight + 6), 6)
|
||||
end
|
||||
end
|
||||
|
||||
if self.distanceFrame then
|
||||
local dScale = tonumber(SFramesDB and SFramesDB.targetDistanceScale) or 1
|
||||
self.distanceFrame:SetScale(Clamp(dScale, 0.7, 1.8))
|
||||
self:ApplyDistanceScale(dScale)
|
||||
end
|
||||
|
||||
if UnitExists("target") then
|
||||
@@ -134,22 +182,36 @@ function SFrames.Target:ApplyConfig()
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.Target:ApplyDistanceScale(scale)
|
||||
local f = self.distanceFrame
|
||||
if not f then return end
|
||||
scale = Clamp(tonumber(scale) or 1, 0.7, 1.8)
|
||||
f:SetWidth(DIST_BASE_WIDTH * scale)
|
||||
f:SetHeight(DIST_BASE_HEIGHT * scale)
|
||||
if f.text then
|
||||
local fontPath = SFrames:GetFont()
|
||||
local outline = (SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
|
||||
local fontSize = math.max(8, math.floor(DIST_BASE_FONTSIZE * scale + 0.5))
|
||||
f.text:SetFont(fontPath, fontSize, outline)
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.Target:InitializeDistanceFrame()
|
||||
local f = CreateFrame("Button", "SFramesTargetDistanceFrame", UIParent)
|
||||
f:SetWidth(80)
|
||||
f:SetHeight(24)
|
||||
f:SetFrameStrata("HIGH")
|
||||
local frameScale = (SFramesDB and type(SFramesDB.targetDistanceScale) == "number") and SFramesDB.targetDistanceScale or 1
|
||||
f:SetScale(frameScale)
|
||||
|
||||
|
||||
local dScale = (SFramesDB and type(SFramesDB.targetDistanceScale) == "number") and SFramesDB.targetDistanceScale or 1
|
||||
dScale = Clamp(dScale, 0.7, 1.8)
|
||||
f:SetWidth(DIST_BASE_WIDTH * dScale)
|
||||
f:SetHeight(DIST_BASE_HEIGHT * dScale)
|
||||
|
||||
if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["TargetDistanceFrame"] then
|
||||
local pos = SFramesDB.Positions["TargetDistanceFrame"]
|
||||
f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs)
|
||||
else
|
||||
-- Default position: Center of screen for visibility if first time
|
||||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 100)
|
||||
end
|
||||
|
||||
|
||||
f:SetMovable(true)
|
||||
f:EnableMouse(true)
|
||||
f:RegisterForDrag("LeftButton")
|
||||
@@ -161,29 +223,29 @@ function SFrames.Target:InitializeDistanceFrame()
|
||||
local point, relativeTo, relativePoint, xOfs, yOfs = this:GetPoint()
|
||||
SFramesDB.Positions["TargetDistanceFrame"] = { point = point, relativePoint = relativePoint, xOfs = xOfs, yOfs = yOfs }
|
||||
end)
|
||||
|
||||
|
||||
SFrames:CreateUnitBackdrop(f)
|
||||
f:SetBackdrop(nil) -- Remove border and background for natural look
|
||||
|
||||
f.text = SFrames:CreateFontString(f, 14, "CENTER")
|
||||
f:SetBackdrop(nil)
|
||||
|
||||
local fontSize = math.max(8, math.floor(DIST_BASE_FONTSIZE * dScale + 0.5))
|
||||
f.text = SFrames:CreateFontString(f, fontSize, "CENTER")
|
||||
f.text:SetPoint("CENTER", f, "CENTER", 0, 0)
|
||||
f.text:SetTextColor(1, 0.8, 0.2)
|
||||
f.text:SetShadowColor(0, 0, 0, 1)
|
||||
f.text:SetShadowOffset(1, -1)
|
||||
|
||||
|
||||
SFrames.Target.distanceFrame = f
|
||||
f:Hide()
|
||||
|
||||
-- Distance Updater on the frame itself
|
||||
|
||||
f.timer = 0
|
||||
f:SetScript("OnUpdate", function()
|
||||
if SFramesDB and SFramesDB.targetDistanceEnabled == false then
|
||||
if this:IsShown() then this:Hide() end
|
||||
return
|
||||
end
|
||||
if not UnitExists("target") then
|
||||
if not UnitExists("target") then
|
||||
if this:IsShown() then this:Hide() end
|
||||
return
|
||||
return
|
||||
end
|
||||
this.timer = this.timer + (arg1 or 0)
|
||||
if this.timer >= 0.4 then
|
||||
@@ -391,7 +453,13 @@ function SFrames.Target:Initialize()
|
||||
SFrames:RegisterEvent("UNIT_MAXRAGE", function() if arg1 == "target" then self:UpdatePower() end end)
|
||||
SFrames:RegisterEvent("PLAYER_COMBO_POINTS", function() self:UpdateComboPoints() end)
|
||||
SFrames:RegisterEvent("UNIT_DISPLAYPOWER", function() if arg1 == "target" then self:UpdatePowerType() end end)
|
||||
SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function() if arg1 == "target" then self.frame.portrait:SetUnit("target") self.frame.portrait:SetCamera(0) self.frame.portrait:SetPosition(-1.0, 0, 0) end end)
|
||||
SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function()
|
||||
if arg1 == "target" and self.frame.portrait and not (SFramesDB and SFramesDB.targetShowPortrait == false) then
|
||||
self.frame.portrait:SetUnit("target")
|
||||
self.frame.portrait:SetCamera(0)
|
||||
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
||||
end
|
||||
end)
|
||||
SFrames:RegisterEvent("UNIT_DYNAMIC_FLAGS", function() if arg1 == "target" then self:UpdateAll() end end)
|
||||
SFrames:RegisterEvent("UNIT_FACTION", function() if arg1 == "target" then self:UpdateAll() end end)
|
||||
SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcon() end)
|
||||
@@ -413,7 +481,15 @@ function SFrames.Target:Initialize()
|
||||
-- If target already exists on load (e.g. after /reload), show and update it immediately
|
||||
self:OnTargetChanged()
|
||||
|
||||
-- Distance Updater removed from target frame
|
||||
-- Register movers
|
||||
if SFrames.Movers and SFrames.Movers.RegisterMover then
|
||||
SFrames.Movers:RegisterMover("TargetFrame", f, "目标",
|
||||
"CENTER", "UIParent", "CENTER", 200, -100)
|
||||
if SFrames.Target.distanceFrame then
|
||||
SFrames.Movers:RegisterMover("TargetDistanceFrame", SFrames.Target.distanceFrame, "目标距离",
|
||||
"CENTER", "UIParent", "CENTER", 0, 100)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SFrames.Target:OnTargetChanged()
|
||||
@@ -444,11 +520,14 @@ function SFrames.Target:UpdateAll()
|
||||
self:UpdateRaidIcon()
|
||||
self:UpdateAuras()
|
||||
|
||||
self.frame.portrait:SetUnit("target")
|
||||
self.frame.portrait:SetCamera(0)
|
||||
self.frame.portrait:Hide()
|
||||
self.frame.portrait:Show()
|
||||
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
||||
local showPortrait = not (SFramesDB and SFramesDB.targetShowPortrait == false)
|
||||
if showPortrait and self.frame.portrait then
|
||||
self.frame.portrait:SetUnit("target")
|
||||
self.frame.portrait:SetCamera(0)
|
||||
self.frame.portrait:Hide()
|
||||
self.frame.portrait:Show()
|
||||
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
||||
end
|
||||
|
||||
local name = UnitName("target") or ""
|
||||
local level = UnitLevel("target")
|
||||
@@ -901,11 +980,11 @@ function SFrames.Target:UpdateAuras()
|
||||
if texture then
|
||||
b.icon:SetTexture(texture)
|
||||
|
||||
-- Scrape tooltip for duration
|
||||
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
|
||||
SFrames.Tooltip:ClearLines()
|
||||
SFrames.Tooltip:SetUnitBuff("target", i)
|
||||
local timeLeft = SFrames:GetAuraTimeLeft("target", i, true)
|
||||
SFrames.Tooltip:Hide()
|
||||
if timeLeft and timeLeft > 0 then
|
||||
b.expirationTime = GetTime() + timeLeft
|
||||
b.cdText:SetText(SFrames:FormatTime(timeLeft))
|
||||
@@ -974,6 +1053,7 @@ function SFrames.Target:UpdateAuras()
|
||||
SFrames.Tooltip:ClearLines()
|
||||
SFrames.Tooltip:SetUnitDebuff("target", i)
|
||||
timeLeft = SFrames:GetAuraTimeLeft("target", i, false)
|
||||
SFrames.Tooltip:Hide()
|
||||
end
|
||||
|
||||
if timeLeft and timeLeft > 0 then
|
||||
|
||||
BIN
img/map_f_1.tga
Normal file
BIN
img/map_f_1.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
img/map_f_2.tga
Normal file
BIN
img/map_f_2.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
Reference in New Issue
Block a user