完成多出修改

修复拾取界面点击无效问题
修复宠物训练界面不显示训练点问题
新增天赋分享到聊天界面
修复飞行界面无法关闭问题
修复术士宠物的显示问题
为天赋界面添加默认数据库支持
框架现在也可自主选择是否启用
玩家框架和目标框架可取消显示3D头像以及透明度修改
背包和银行也添加透明度自定义支持
优化tooltip性能和背包物品显示方式
修复布局模式在ui缩放后不能正常定位的问题
添加硬核模式危险和死亡的工会通报
添加拾取和已拾取的框体
等等
This commit is contained in:
rucky
2026-03-23 10:25:25 +08:00
parent 63337b14d2
commit ec9e3c29d6
34 changed files with 13897 additions and 578 deletions

View File

@@ -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
--------------------------------------------------------------------------------

View File

@@ -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")

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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)

File diff suppressed because it is too large Load Diff

220
Core.lua
View File

@@ -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
@@ -550,12 +677,6 @@ function SFrames:HideBlizzardFrames()
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()

View File

@@ -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 .. " - 飞行路线")

View File

@@ -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
View 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
View 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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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
--------------------------------------------------------------------------------

View File

@@ -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)

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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 = {}

View File

@@ -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 (.+)%%",

View File

@@ -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

View File

@@ -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()

View File

@@ -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
--------------------------------------------------------------------------------

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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,19 +182,33 @@ 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
@@ -163,9 +225,10 @@ function SFrames.Target:InitializeDistanceFrame()
end)
SFrames:CreateUnitBackdrop(f)
f:SetBackdrop(nil) -- Remove border and background for natural look
f:SetBackdrop(nil)
f.text = SFrames:CreateFontString(f, 14, "CENTER")
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)
@@ -174,7 +237,6 @@ function SFrames.Target:InitializeDistanceFrame()
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
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
img/map_f_2.tga Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB