Files
Nanami-DPS/Window.lua
2026-03-25 00:57:35 +08:00

1512 lines
54 KiB
Lua

local NanamiDPS = NanamiDPS
local DataStore = NanamiDPS.DataStore
local BarDisplay = NanamiDPS.BarDisplay
local TooltipMod = NanamiDPS.Tooltip
local L = NanamiDPS.L
local Window = {}
NanamiDPS.Window = Window
local MAX_WINDOWS = 5
local activeWindows = {}
local SNAP_THRESHOLD = 12
-----------------------------------------------------------------------
-- Scrollable dropdown menu
-----------------------------------------------------------------------
local MAX_DROPDOWN_VISIBLE = 8
local dropdownOverlay = CreateFrame("Button", "NanamiDPS_DropdownOverlay", UIParent)
dropdownOverlay:SetFrameStrata("FULLSCREEN")
dropdownOverlay:SetAllPoints(UIParent)
dropdownOverlay:EnableMouse(true)
dropdownOverlay:Hide()
dropdownOverlay.activeDropdown = nil
dropdownOverlay:SetScript("OnClick", function()
if dropdownOverlay.activeDropdown then
dropdownOverlay.activeDropdown:Hide()
end
dropdownOverlay.activeDropdown = nil
dropdownOverlay:Hide()
end)
local function HideActiveDropdown()
if dropdownOverlay.activeDropdown then
dropdownOverlay.activeDropdown:Hide()
end
dropdownOverlay.activeDropdown = nil
dropdownOverlay:Hide()
end
local function ShowDropdown(parent, items, onClick, anchorPoint, relTo, relPoint, xOff, yOff)
HideActiveDropdown()
if parent.dropdown and parent.dropdown:IsShown() then
parent.dropdown:Hide()
return
end
if not parent.dropdown then
parent.dropdown = CreateFrame("Frame", nil, parent)
parent.dropdown:SetFrameStrata("TOOLTIP")
SFrames:CreateBackdrop(parent.dropdown)
parent.dropdown:EnableMouseWheel(1)
end
local dd = parent.dropdown
if dd.buttons then
for _, btn in pairs(dd.buttons) do btn:Hide() end
end
dd.buttons = {}
dd.allItems = items
dd.onClickFn = onClick
dd.scrollOffset = 0
local itemH = 18
local padding = 4
local maxW = 80
local totalItems = table.getn(items)
local visibleCount = math.min(totalItems, MAX_DROPDOWN_VISIBLE)
for i = 1, visibleCount do
local btn = CreateFrame("Button", nil, dd)
btn:SetHeight(itemH)
btn.slotIndex = i
btn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 0,
})
btn:SetBackdropColor(0, 0, 0, 0)
local fs = SFrames:CreateFontString(btn, 9, "LEFT")
fs:SetPoint("LEFT", 4, 0)
fs:SetPoint("RIGHT", -4, 0)
btn.label = fs
local A = SFrames.ActiveTheme
btn:SetScript("OnEnter", function()
if A and A.buttonHoverBg then
this:SetBackdropColor(A.buttonHoverBg[1], A.buttonHoverBg[2], A.buttonHoverBg[3], 0.6)
else
this:SetBackdropColor(0.3, 0.3, 0.3, 0.6)
end
end)
btn:SetScript("OnLeave", function()
this:SetBackdropColor(0, 0, 0, 0)
end)
btn:SetScript("OnClick", function()
if this.itemData then
dd.onClickFn(this.itemData)
HideActiveDropdown()
end
end)
table.insert(dd.buttons, btn)
end
local function RefreshDropdownButtons()
local offset = dd.scrollOffset
for idx, btn in ipairs(dd.buttons) do
local dataIdx = idx + offset
if dataIdx <= totalItems then
local item = dd.allItems[dataIdx]
btn.itemData = item
btn.label:SetText(item.text or item.name or "")
btn:ClearAllPoints()
btn:SetPoint("TOPLEFT", dd, "TOPLEFT", padding, -padding - (idx - 1) * itemH)
btn:SetPoint("TOPRIGHT", dd, "TOPRIGHT", -padding, -padding - (idx - 1) * itemH)
btn:Show()
local strW = btn.label:GetStringWidth()
if strW and strW + 20 > maxW then maxW = strW + 20 end
else
btn:Hide()
end
end
dd:SetWidth(math.max(maxW, 90))
end
RefreshDropdownButtons()
dd:SetScript("OnMouseWheel", function()
local maxOffset = math.max(0, totalItems - visibleCount)
if arg1 > 0 then
dd.scrollOffset = math.max(0, dd.scrollOffset - 1)
else
dd.scrollOffset = math.min(maxOffset, dd.scrollOffset + 1)
end
RefreshDropdownButtons()
end)
local ddHeight = visibleCount * itemH + padding * 2
if totalItems > MAX_DROPDOWN_VISIBLE then
ddHeight = ddHeight + 8
end
dd:SetHeight(ddHeight)
dd:ClearAllPoints()
dd:SetPoint(anchorPoint or "TOPLEFT", relTo or parent, relPoint or "BOTTOMLEFT", xOff or 0, yOff or -2)
-- Scroll indicator for long lists
if not dd.scrollUpIndicator then
dd.scrollUpIndicator = SFrames:CreateFontString(dd, 8, "CENTER")
dd.scrollUpIndicator:SetPoint("TOP", dd, "TOP", 0, -1)
dd.scrollUpIndicator:SetText("|cff888888▲")
end
if not dd.scrollDownIndicator then
dd.scrollDownIndicator = SFrames:CreateFontString(dd, 8, "CENTER")
dd.scrollDownIndicator:SetPoint("BOTTOM", dd, "BOTTOM", 0, 1)
dd.scrollDownIndicator:SetText("|cff888888▼")
end
if totalItems > MAX_DROPDOWN_VISIBLE then
dd.scrollUpIndicator:Show()
dd.scrollDownIndicator:Show()
else
dd.scrollUpIndicator:Hide()
dd.scrollDownIndicator:Hide()
end
dropdownOverlay.activeDropdown = dd
dropdownOverlay:Show()
dd:Show()
end
-----------------------------------------------------------------------
-- Magnetic snapping
-----------------------------------------------------------------------
local function SnapWindow(frame)
local left = frame:GetLeft()
local bottom = frame:GetBottom()
local right = frame:GetRight()
local top = frame:GetTop()
local w = frame:GetWidth()
local h = frame:GetHeight()
if not left or not bottom then return end
local snapX, snapY = nil, nil
local bestDistX, bestDistY = SNAP_THRESHOLD + 1, SNAP_THRESHOLD + 1
for _, other in pairs(activeWindows) do
if other and other ~= frame and other:IsShown() then
local oL = other:GetLeft()
local oB = other:GetBottom()
local oR = other:GetRight()
local oT = other:GetTop()
if oL and oB then
-- Snap right edge to other's left edge
local d = math.abs(right - oL)
if d < bestDistX then bestDistX = d; snapX = oL - w end
-- Snap left edge to other's right edge
d = math.abs(left - oR)
if d < bestDistX then bestDistX = d; snapX = oR end
-- Snap left to left
d = math.abs(left - oL)
if d < bestDistX then bestDistX = d; snapX = oL end
-- Snap right to right
d = math.abs(right - oR)
if d < bestDistX then bestDistX = d; snapX = oR - w end
-- Snap top to top
d = math.abs(top - oT)
if d < bestDistY then bestDistY = d; snapY = oT - h end
-- Snap bottom to bottom
d = math.abs(bottom - oB)
if d < bestDistY then bestDistY = d; snapY = oB end
-- Snap top to other's bottom
d = math.abs(top - oB)
if d < bestDistY then bestDistY = d; snapY = oB - h end
-- Snap bottom to other's top
d = math.abs(bottom - oT)
if d < bestDistY then bestDistY = d; snapY = oT end
end
end
end
-- Screen edge snapping
local screenW = GetScreenWidth()
local screenH = GetScreenHeight()
local uiScale = UIParent:GetEffectiveScale()
local d = math.abs(left)
if d < bestDistX then bestDistX = d; snapX = 0 end
d = math.abs(right - screenW)
if d < bestDistX then bestDistX = d; snapX = screenW - w end
d = math.abs(bottom)
if d < bestDistY then bestDistY = d; snapY = 0 end
d = math.abs(top - screenH)
if d < bestDistY then bestDistY = d; snapY = screenH - h end
if bestDistX <= SNAP_THRESHOLD or bestDistY <= SNAP_THRESHOLD then
local finalX = (bestDistX <= SNAP_THRESHOLD and snapX) or left
local finalY = (bestDistY <= SNAP_THRESHOLD and snapY) or bottom
frame:ClearAllPoints()
frame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", finalX, finalY)
end
end
-----------------------------------------------------------------------
-- Window creation
-----------------------------------------------------------------------
function Window:Create(wid)
local cfg = NanamiDPS.config or {}
local barHeight = cfg.barHeight or 16
local frame = CreateFrame("Frame", "NanamiDPSWindow" .. wid, UIParent)
frame:SetWidth(220)
frame:SetHeight(barHeight * 8 + 26)
frame:SetClampedToScreen(true)
frame:SetMovable(true)
frame:SetResizable(true)
frame:SetMinResize(160, 50)
frame:EnableMouse(true)
frame:EnableMouseWheel(1)
SFrames:CreateRoundBackdrop(frame)
local A = SFrames.ActiveTheme
if A and A.panelBg then
frame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], cfg.backdropAlpha or 0.92)
end
if A and A.panelBorder then
frame:SetBackdropBorderColor(A.panelBorder[1], A.panelBorder[2], A.panelBorder[3], A.panelBorder[4] or 0.9)
end
frame:SetID(wid)
frame.scroll = 0
frame.segmentIndex = 1
frame.activeModuleName = "DamageDone"
frame.bars = {}
---------------------------------------------------------------
-- Title bar
---------------------------------------------------------------
frame.titleBar = CreateFrame("Frame", nil, frame)
frame.titleBar:SetHeight(20)
frame.titleBar:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -4)
frame.titleBar:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
frame.titleBar:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 0,
})
if A and A.headerBg then
frame.titleBar:SetBackdropColor(A.headerBg[1], A.headerBg[2], A.headerBg[3], A.headerBg[4] or 0.98)
else
frame.titleBar:SetBackdropColor(0.06, 0.06, 0.08, 0.98)
end
frame.titleBar:EnableMouse(true)
frame.titleBar:RegisterForDrag("LeftButton")
frame.titleBar:SetScript("OnDragStart", function()
if not cfg.locked then frame:StartMoving() end
end)
frame.titleBar:SetScript("OnDragStop", function()
frame:StopMovingOrSizing()
SnapWindow(frame)
Window:SavePosition(frame)
end)
-- Title label
frame.titleText = SFrames:CreateFontString(frame.titleBar, 10, "LEFT")
frame.titleText:SetPoint("LEFT", frame.titleBar, "LEFT", 6, 0)
local accentHex = (A and A.accentHex) or "ffFF88AA"
frame.titleText:SetText("|c" .. accentHex .. "Nanami|r DPS")
if A and A.title then
frame.titleText:SetTextColor(A.title[1], A.title[2], A.title[3])
end
---------------------------------------------------------------
-- Title bar buttons (right side)
---------------------------------------------------------------
-- Close / Hide button
local closeBtn = CreateFrame("Button", nil, frame.titleBar)
closeBtn:SetWidth(16)
closeBtn:SetHeight(16)
closeBtn:SetPoint("RIGHT", frame.titleBar, "RIGHT", -4, 0)
local closeIcon = SFrames:CreateIcon(closeBtn, "close", 12)
closeIcon:SetPoint("CENTER", 0, 0)
closeBtn:SetScript("OnClick", function()
if wid > 1 then
Window:DestroyWindow(wid)
else
frame:Hide()
end
end)
closeBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:AddLine(L["Hide Window"])
GameTooltip:Show()
end)
closeBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
-- New window button (+)
local newWinBtn = CreateFrame("Button", nil, frame.titleBar)
newWinBtn:SetWidth(16)
newWinBtn:SetHeight(16)
newWinBtn:SetPoint("RIGHT", closeBtn, "LEFT", -1, 0)
local newWinIcon = SFrames:CreateIcon(newWinBtn, "save", 12)
newWinIcon:SetPoint("CENTER", 0, 0)
newWinBtn:SetScript("OnClick", function()
Window:CreateNewWindow()
end)
newWinBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:AddLine(L["Create New Window"])
GameTooltip:Show()
if A and A.accent then
newWinIcon:SetVertexColor(A.accent[1], A.accent[2], A.accent[3])
end
end)
newWinBtn:SetScript("OnLeave", function()
GameTooltip:Hide()
newWinIcon:SetVertexColor(1, 1, 1)
end)
-- Settings button
local settingsBtn = CreateFrame("Button", nil, frame.titleBar)
settingsBtn:SetWidth(16)
settingsBtn:SetHeight(16)
settingsBtn:SetPoint("RIGHT", newWinBtn, "LEFT", -1, 0)
local settingsIcon = SFrames:CreateIcon(settingsBtn, "settings", 12)
settingsIcon:SetPoint("CENTER", 0, 0)
settingsBtn:SetScript("OnClick", function()
if NanamiDPS.Options then NanamiDPS.Options:Toggle() end
end)
settingsBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:AddLine(L["Settings"])
GameTooltip:Show()
end)
settingsBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
-- Reset button
local resetBtn = CreateFrame("Button", nil, frame.titleBar)
resetBtn:SetWidth(16)
resetBtn:SetHeight(16)
resetBtn:SetPoint("RIGHT", settingsBtn, "LEFT", -1, 0)
local resetIcon = SFrames:CreateIcon(resetBtn, "close", 10)
resetIcon:SetPoint("CENTER", 0, 0)
if A and A.accent then
resetIcon:SetVertexColor(A.accent[1], A.accent[2], A.accent[3])
else
resetIcon:SetVertexColor(1, 0.4, 0.4)
end
resetBtn:SetScript("OnClick", function()
if IsShiftKeyDown() then
DataStore:ResetAll()
else
local dialog = StaticPopupDialogs["NANAMI_DPS_CONFIRM"]
dialog.text = L["Reset Data?"]
dialog.OnAccept = function() DataStore:ResetAll() end
StaticPopup_Show("NANAMI_DPS_CONFIRM")
end
end)
resetBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:AddLine(L["Reset"])
GameTooltip:AddLine("|cffffffff" .. L["Shift-click to reset"])
GameTooltip:Show()
end)
resetBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
-- Pause button
local pauseBtn = CreateFrame("Button", nil, frame.titleBar)
pauseBtn:SetWidth(16)
pauseBtn:SetHeight(16)
pauseBtn:SetPoint("RIGHT", resetBtn, "LEFT", -1, 0)
local pauseIcon = SFrames:CreateFontString(pauseBtn, 9, "CENTER")
pauseIcon:SetAllPoints()
pauseIcon:SetText("||")
if A and A.text then
pauseIcon:SetTextColor(A.text[1], A.text[2], A.text[3])
end
frame.pauseBtn = pauseBtn
local function UpdatePauseVisual()
if cfg.paused then
if A and A.accent then
pauseIcon:SetTextColor(A.accent[1], A.accent[2], A.accent[3])
else
pauseIcon:SetTextColor(1, 0.5, 0.5)
end
pauseIcon:SetText("|cffff4444>||")
else
if A and A.text then
pauseIcon:SetTextColor(A.text[1], A.text[2], A.text[3])
else
pauseIcon:SetTextColor(1, 1, 1)
end
pauseIcon:SetText("||")
end
end
UpdatePauseVisual()
frame.updatePauseVisual = UpdatePauseVisual
pauseBtn:SetScript("OnClick", function()
cfg.paused = not cfg.paused
Window:UpdatePausedAlpha()
end)
pauseBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
if cfg.paused then
GameTooltip:AddLine(L["Resume"])
else
GameTooltip:AddLine(L["Pause"])
end
GameTooltip:Show()
end)
pauseBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
-- Report button
local reportBtn = CreateFrame("Button", nil, frame.titleBar)
reportBtn:SetWidth(16)
reportBtn:SetHeight(16)
reportBtn:SetPoint("RIGHT", pauseBtn, "LEFT", -1, 0)
local reportIcon = SFrames:CreateFontString(reportBtn, 9, "CENTER")
reportIcon:SetAllPoints()
reportIcon:SetText("R")
if A and A.text then
reportIcon:SetTextColor(A.text[1], A.text[2], A.text[3])
end
reportBtn:SetScript("OnClick", function()
Window:ShowReport(frame)
end)
reportBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:AddLine(L["Report to Chat"])
GameTooltip:Show()
if A and A.accent then
reportIcon:SetTextColor(A.accent[1], A.accent[2], A.accent[3])
end
end)
reportBtn:SetScript("OnLeave", function()
GameTooltip:Hide()
if A and A.text then
reportIcon:SetTextColor(A.text[1], A.text[2], A.text[3])
else
reportIcon:SetTextColor(1, 1, 1)
end
end)
---------------------------------------------------------------
-- Segment / Mode selectors (below title)
---------------------------------------------------------------
frame.selectorBar = CreateFrame("Frame", nil, frame)
frame.selectorBar:SetHeight(16)
frame.selectorBar:SetPoint("TOPLEFT", frame.titleBar, "BOTTOMLEFT", 0, 0)
frame.selectorBar:SetPoint("TOPRIGHT", frame.titleBar, "BOTTOMRIGHT", 0, 0)
frame.selectorBar:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 0,
})
if A and A.sectionBg then
frame.selectorBar:SetBackdropColor(A.sectionBg[1], A.sectionBg[2], A.sectionBg[3], A.sectionBg[4] or 0.8)
else
frame.selectorBar:SetBackdropColor(0.08, 0.04, 0.06, 0.8)
end
-- Segment button
frame.btnSegment = CreateFrame("Button", nil, frame.selectorBar)
frame.btnSegment:SetHeight(14)
frame.btnSegment:SetPoint("LEFT", frame.selectorBar, "LEFT", 4, 0)
frame.btnSegment:SetWidth(70)
frame.btnSegment:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
if A and A.buttonBg then
frame.btnSegment:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
frame.btnSegment:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
else
frame.btnSegment:SetBackdropColor(0.15, 0.08, 0.12, 0.9)
frame.btnSegment:SetBackdropBorderColor(0.4, 0.3, 0.35, 0.8)
end
frame.btnSegment.text = SFrames:CreateFontString(frame.btnSegment, 9, "CENTER")
frame.btnSegment.text:SetAllPoints()
frame.btnSegment.text:SetText(L["Current"])
frame.btnSegment:SetScript("OnClick", function()
local items = {}
local segList = DataStore:GetSegmentList()
for _, seg in ipairs(segList) do
table.insert(items, { text = seg.name, index = seg.index })
end
ShowDropdown(frame.btnSegment, items, function(item)
frame.segmentIndex = item.index
frame.btnSegment.text:SetText(item.text)
Window:SavePosition(frame)
Window:RefreshWindow(frame)
end, "TOPLEFT", frame.btnSegment, "BOTTOMLEFT", 0, -2)
end)
-- Mode button
frame.btnMode = CreateFrame("Button", nil, frame.selectorBar)
frame.btnMode:SetHeight(14)
frame.btnMode:SetPoint("LEFT", frame.btnSegment, "RIGHT", 4, 0)
frame.btnMode:SetPoint("RIGHT", frame.selectorBar, "RIGHT", -4, 0)
frame.btnMode:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
if A and A.buttonBg then
frame.btnMode:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
frame.btnMode:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
else
frame.btnMode:SetBackdropColor(0.15, 0.08, 0.12, 0.9)
frame.btnMode:SetBackdropBorderColor(0.4, 0.3, 0.35, 0.8)
end
frame.btnMode.text = SFrames:CreateFontString(frame.btnMode, 9, "CENTER")
frame.btnMode.text:SetAllPoints()
frame.btnMode.text:SetText(L["Damage Done"])
frame.btnMode:SetScript("OnClick", function()
local items = {}
for _, name in ipairs(NanamiDPS.moduleOrder) do
local mod = NanamiDPS.modules[name]
if mod then
table.insert(items, { text = mod:GetName(), moduleName = name })
end
end
ShowDropdown(frame.btnMode, items, function(item)
frame.activeModuleName = item.moduleName
frame.btnMode.text:SetText(item.text)
frame.scroll = 0
Window:SavePosition(frame)
Window:RefreshWindow(frame)
end, "TOPLEFT", frame.btnMode, "BOTTOMLEFT", 0, -2)
end)
---------------------------------------------------------------
-- Content area (bars go here)
---------------------------------------------------------------
frame.content = CreateFrame("Frame", nil, frame)
frame.content:SetPoint("TOPLEFT", frame.selectorBar, "BOTTOMLEFT", 0, -1)
frame.content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -4, 4)
-- Paused overlay text (created after content frame)
frame.pausedOverlay = frame.content:CreateFontString(nil, "OVERLAY")
frame.pausedOverlay:SetFont(STANDARD_TEXT_FONT, 14, "OUTLINE")
frame.pausedOverlay:SetPoint("CENTER", frame.content, "CENTER", 0, 0)
frame.pausedOverlay:SetText("|cffff6666" .. L["Paused"])
frame.pausedOverlay:SetAlpha(0.7)
if cfg.paused then
frame.pausedOverlay:Show()
else
frame.pausedOverlay:Hide()
end
---------------------------------------------------------------
-- Pause alpha: restore on hover, reduce on leave
---------------------------------------------------------------
frame:SetScript("OnEnter", function()
if cfg.paused then
this:SetAlpha(cfg.backdropAlpha or 0.92)
end
end)
frame:SetScript("OnLeave", function()
if cfg.paused and not MouseIsOver(this) then
this:SetAlpha(cfg.pausedAlpha or 0.35)
end
end)
---------------------------------------------------------------
-- Scroll wheel
---------------------------------------------------------------
frame:SetScript("OnMouseWheel", function()
local delta = arg1
frame.scroll = frame.scroll + (delta > 0 and -1 or 1)
frame.scroll = math.max(frame.scroll, 0)
Window:RefreshWindow(frame)
end)
---------------------------------------------------------------
-- Resize handle
---------------------------------------------------------------
frame.resizeBtn = CreateFrame("Frame", nil, frame)
frame.resizeBtn:SetWidth(16)
frame.resizeBtn:SetHeight(16)
frame.resizeBtn:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -3, 3)
frame.resizeBtn:EnableMouse(true)
frame.resizeBtn:SetFrameLevel(frame:GetFrameLevel() + 10)
frame.resizeBtn.tex = frame.resizeBtn:CreateTexture(nil, "OVERLAY")
frame.resizeBtn.tex:SetAllPoints()
frame.resizeBtn.tex:SetTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
if A and A.accent then
frame.resizeBtn.tex:SetVertexColor(A.accent[1], A.accent[2], A.accent[3], 0.6)
else
frame.resizeBtn.tex:SetVertexColor(0.6, 0.6, 0.6, 0.5)
end
frame.resizeBtn:SetScript("OnEnter", function()
if not cfg.locked then
this.tex:SetTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Highlight")
if A and A.accent then
this.tex:SetVertexColor(A.accent[1], A.accent[2], A.accent[3], 1)
else
this.tex:SetVertexColor(1, 1, 1, 0.9)
end
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT")
GameTooltip:AddLine(L["Drag to Resize"])
GameTooltip:Show()
end
end)
frame.resizeBtn:SetScript("OnLeave", function()
this.tex:SetTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
if A and A.accent then
this.tex:SetVertexColor(A.accent[1], A.accent[2], A.accent[3], 0.6)
else
this.tex:SetVertexColor(0.6, 0.6, 0.6, 0.5)
end
GameTooltip:Hide()
end)
frame.resizeBtn:SetScript("OnMouseDown", function()
if not cfg.locked then
frame.sizing = true
this.tex:SetTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Down")
frame:StartSizing("BOTTOMRIGHT")
end
end)
frame.resizeBtn:SetScript("OnMouseUp", function()
frame.sizing = nil
this.tex:SetTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Highlight")
frame:StopMovingOrSizing()
SnapWindow(frame)
Window:SavePosition(frame)
Window:RefreshWindow(frame, true)
end)
---------------------------------------------------------------
-- Periodic refresh
---------------------------------------------------------------
frame:SetScript("OnUpdate", function()
if (this.tick or 0) > GetTime() then return end
this.tick = GetTime() + 0.3
if this.needsRefresh then
this.needsRefresh = nil
Window:RefreshWindow(this)
end
if cfg.locked then
this.resizeBtn:SetAlpha(0)
this.resizeBtn:EnableMouse(false)
else
this.resizeBtn:EnableMouse(true)
if MouseIsOver(this) then
this.resizeBtn:SetAlpha(1)
else
this.resizeBtn:SetAlpha(0.4)
end
end
end)
---------------------------------------------------------------
-- Position loading
---------------------------------------------------------------
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function()
Window:LoadPosition(frame)
end)
return frame
end
function Window:SavePosition(frame)
if not NanamiDPS_DB then return end
NanamiDPS_DB.windowPositions = NanamiDPS_DB.windowPositions or {}
local wid = frame:GetID()
local l = frame:GetLeft()
local b = frame: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 fw = frame:GetWidth() or 0
local fh = frame:GetHeight() or 0
local cx = l + fw / 2
local cy = b + fh / 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 + fh))
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 = frame:GetRight() or 0
if cy > screenH * 2 / 3 then
point = "TOPRIGHT"; relPoint = "TOPRIGHT"
xOfs = r - screenW; yOfs = -(screenH - (b + fh))
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 + fh))
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
NanamiDPS_DB.windowPositions[wid] = {
point = point, relativePoint = relPoint,
xOfs = xOfs, yOfs = yOfs,
w = fw, h = fh,
moduleName = frame.activeModuleName,
segmentIndex = frame.segmentIndex,
}
frame:ClearAllPoints()
frame:SetPoint(point, UIParent, relPoint, xOfs, yOfs)
end
function Window:LoadPosition(frame)
if not NanamiDPS_DB or not NanamiDPS_DB.windowPositions then
frame:ClearAllPoints()
frame:SetPoint("RIGHT", UIParent, "RIGHT", -80, -50)
return
end
local wid = frame:GetID()
local pos = NanamiDPS_DB.windowPositions[wid]
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)
if pos.w then frame:SetWidth(pos.w) end
if pos.h then frame:SetHeight(pos.h) end
elseif pos and pos.left and pos.bottom then
frame:ClearAllPoints()
frame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", pos.left, pos.bottom)
if pos.w then frame:SetWidth(pos.w) end
if pos.h then frame:SetHeight(pos.h) end
elseif pos and pos.x and pos.y then
frame:ClearAllPoints()
frame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", pos.x, pos.y)
if pos.w then frame:SetWidth(pos.w) end
if pos.h then frame:SetHeight(pos.h) end
else
frame:ClearAllPoints()
frame:SetPoint("RIGHT", UIParent, "RIGHT", -80, -50)
end
if pos then
if pos.moduleName and NanamiDPS.modules[pos.moduleName] then
frame.activeModuleName = pos.moduleName
local mod = NanamiDPS.modules[pos.moduleName]
if mod and mod.GetName and frame.btnMode then
frame.btnMode.text:SetText(mod:GetName())
end
end
if pos.segmentIndex ~= nil then
frame.segmentIndex = pos.segmentIndex
if frame.btnSegment then
local segList = DataStore:GetSegmentList()
for _, s in ipairs(segList) do
if s.index == pos.segmentIndex then
frame.btnSegment.text:SetText(s.name)
break
end
end
end
end
end
end
-----------------------------------------------------------------------
-- Refresh logic
-----------------------------------------------------------------------
function Window:RefreshWindow(frame, force)
if not frame then return end
local mod = NanamiDPS.modules[frame.activeModuleName]
if not mod then return end
local seg = DataStore:GetSegment(frame.segmentIndex or 1)
if not seg then return end
local barData = mod:GetBars(seg)
if not barData then barData = {} end
local maxBars = BarDisplay:GetMaxBars(frame)
local totalEntries = table.getn(barData)
frame.scroll = math.min(frame.scroll, math.max(0, totalEntries - maxBars))
local maxVal = 0
for _, d in ipairs(barData) do
if (d.value or 0) > maxVal then maxVal = d.value end
end
local barsNeeded = math.max(0, math.min(maxBars, totalEntries - frame.scroll))
while table.getn(frame.bars) < barsNeeded do
local bar = BarDisplay:CreateBar(frame, table.getn(frame.bars) + 1)
table.insert(frame.bars, bar)
end
for i = 1, math.max(table.getn(frame.bars), barsNeeded) do
local dataIdx = i + frame.scroll
local bar = frame.bars[i]
if not bar then break end
if dataIdx <= totalEntries then
local d = barData[dataIdx]
BarDisplay:UpdateBar(bar, d, maxVal, dataIdx)
BarDisplay:SetBarCallbacks(bar,
function(b) TooltipMod:ShowBar(b, frame) end,
function(b) TooltipMod:Hide() end,
function(b, btn)
if b.barData and NanamiDPS.DetailView then
NanamiDPS.DetailView:Show(b.barData, frame.activeModuleName, frame.segmentIndex or 1)
end
end
)
else
bar:Hide()
end
end
BarDisplay:LayoutBars(frame)
end
function Window:UpdatePausedAlpha()
local cfg = NanamiDPS.config or {}
for _, win in pairs(activeWindows) do
if win and win:IsShown() then
if cfg.paused then
win:SetAlpha(cfg.pausedAlpha or 0.35)
if win.pausedOverlay then
win.pausedOverlay:Show()
end
else
win:SetAlpha(1.0)
if win.pausedOverlay then
win.pausedOverlay:Hide()
end
end
if win.updatePauseVisual then
win.updatePauseVisual()
end
end
end
end
function Window:RefreshAll(force)
for _, win in pairs(activeWindows) do
if win and win:IsShown() then
self:RefreshWindow(win, force)
end
end
end
-----------------------------------------------------------------------
-- Initialization
-----------------------------------------------------------------------
NanamiDPS:RegisterCallback("INIT", "Window", function()
NanamiDPS:RegisterCallback("refresh", "WindowRefresh", function()
for _, w in pairs(activeWindows) do
if w then w.needsRefresh = true end
end
end)
local cfg = NanamiDPS.config or {}
local savedWindowCount = 1
if NanamiDPS_DB and NanamiDPS_DB.windowPositions then
for wid, _ in pairs(NanamiDPS_DB.windowPositions) do
if type(wid) == "number" and wid > savedWindowCount then
savedWindowCount = wid
end
end
end
for i = 1, savedWindowCount do
local win = Window:Create(i)
activeWindows[i] = win
Window:LoadPosition(win)
if cfg.visible ~= false then
win:Show()
else
win:Hide()
end
Window:RefreshWindow(win, true)
end
NanamiDPS.windows = activeWindows
end)
-----------------------------------------------------------------------
-- Multi-window API
-----------------------------------------------------------------------
function Window:CreateNewWindow()
for i = 2, MAX_WINDOWS do
if not activeWindows[i] then
local win = self:Create(i)
activeWindows[i] = win
local prev = activeWindows[i - 1]
if prev and prev:IsShown() then
win:ClearAllPoints()
win:SetPoint("TOPLEFT", prev, "TOPRIGHT", 0, 0)
win:SetWidth(prev:GetWidth())
win:SetHeight(prev:GetHeight())
end
win:Show()
self:RefreshWindow(win, true)
self:SavePosition(win)
return win
end
end
local chatHex = (SFrames.ActiveTheme and SFrames.ActiveTheme.accentHex) or "ffFF88AA"
DEFAULT_CHAT_FRAME:AddMessage("|c" .. chatHex .. "Nanami DPS|r: " .. L["Max Segments"] .. " (" .. MAX_WINDOWS .. ")")
end
function Window:DestroyWindow(wid)
if wid == 1 then return end
if activeWindows[wid] then
activeWindows[wid]:Hide()
activeWindows[wid] = nil
if NanamiDPS_DB and NanamiDPS_DB.windowPositions then
NanamiDPS_DB.windowPositions[wid] = nil
end
end
end
function Window:ToggleVisibility()
for _, win in pairs(activeWindows) do
if win then
if win:IsShown() then win:Hide() else win:Show() end
end
end
end
-----------------------------------------------------------------------
-- Report to Chat Panel
-----------------------------------------------------------------------
local reportFrame = nil
local reportChannel = "SAY"
local reportCount = 5
local reportSourceWin = nil
local reportHeaderFn = nil
local reportLinesFn = nil
local CHANNEL_LIST = {
{ id = "SAY", key = "Say" },
{ id = "YELL", key = "Yell" },
{ id = "PARTY", key = "Party" },
{ id = "RAID", key = "Raid" },
{ id = "GUILD", key = "Guild" },
{ id = "OFFICER", key = "Officer" },
{ id = "WHISPER", key = "Whisper" },
}
local function UpdateReportChannelButtons()
if not reportFrame or not reportFrame.channelBtns then return end
local A = SFrames.ActiveTheme
for _, btn in ipairs(reportFrame.channelBtns) do
if btn.channelId == reportChannel then
if A and A.accent then
btn:SetBackdropColor(A.accent[1] * 0.3, A.accent[2] * 0.3, A.accent[3] * 0.3, 0.9)
btn:SetBackdropBorderColor(A.accent[1], A.accent[2], A.accent[3], 0.9)
else
btn:SetBackdropColor(0.3, 0.15, 0.2, 0.9)
btn:SetBackdropBorderColor(1, 0.53, 0.67, 0.9)
end
if A and A.accent then
btn.label:SetTextColor(A.accent[1], A.accent[2], A.accent[3])
else
btn.label:SetTextColor(1, 0.85, 0.9)
end
else
if A and A.buttonBg then
btn:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
else
btn:SetBackdropColor(0.15, 0.08, 0.12, 0.9)
end
if A and A.buttonBorder then
btn:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
else
btn:SetBackdropBorderColor(0.4, 0.3, 0.35, 0.8)
end
if A and A.text then
btn.label:SetTextColor(A.text[1], A.text[2], A.text[3])
else
btn.label:SetTextColor(0.8, 0.8, 0.8)
end
end
end
if reportFrame.whisperRow then
if reportChannel == "WHISPER" then
reportFrame.whisperRow:Show()
else
reportFrame.whisperRow:Hide()
end
end
-- Reposition elements below whisper row
local linesY = (reportChannel == "WHISPER") and -118 or -92
if reportFrame.linesRow then
reportFrame.linesRow:ClearAllPoints()
reportFrame.linesRow:SetPoint("TOPLEFT", reportFrame.titleBar, "BOTTOMLEFT", 8, linesY)
reportFrame.linesRow:SetPoint("RIGHT", reportFrame, "RIGHT", -12, 0)
end
if reportFrame.sendBtn then
reportFrame.sendBtn:ClearAllPoints()
reportFrame.sendBtn:SetPoint("TOPLEFT", reportFrame.titleBar, "BOTTOMLEFT", 60, linesY - 36)
end
local frameH = (reportChannel == "WHISPER") and 230 or 200
reportFrame:SetHeight(frameH)
end
local function GetSegmentName(win)
if not win then return "" end
local idx = win.segmentIndex or 1
if idx == 0 then return L["Total"] end
if idx == 1 then return L["Current"] end
local segList = DataStore:GetSegmentList()
for _, s in ipairs(segList) do
if s.index == idx then return s.name end
end
return "Segment " .. idx
end
local function DoSendReport()
if not reportLinesFn then return end
local lines = reportLinesFn(reportCount)
if not lines or table.getn(lines) == 0 then
local chatHex = (SFrames.ActiveTheme and SFrames.ActiveTheme.accentHex) or "ffFF88AA"
DEFAULT_CHAT_FRAME:AddMessage("|c" .. chatHex .. "Nanami DPS|r: " .. L["No Report Data"])
return
end
local channel = reportChannel
local target = nil
if channel == "WHISPER" then
target = reportFrame.whisperEdit:GetText()
if not target or NanamiDPS.trim(target) == "" then
reportFrame.whisperEdit:SetFocus()
return
end
target = NanamiDPS.trim(target)
end
local header = reportHeaderFn and reportHeaderFn() or "Nanami DPS:"
SendChatMessage(header, channel, nil, target)
for _, line in ipairs(lines) do
SendChatMessage(line, channel, nil, target)
end
local chatHex = (SFrames.ActiveTheme and SFrames.ActiveTheme.accentHex) or "ffFF88AA"
DEFAULT_CHAT_FRAME:AddMessage("|c" .. chatHex .. "Nanami DPS|r: " .. L["Report Sent"])
reportFrame:Hide()
end
local function EnsureReportFrame()
if reportFrame then return reportFrame end
local A = SFrames.ActiveTheme
reportFrame = CreateFrame("Frame", "NanamiDPSReportFrame", UIParent)
reportFrame:SetWidth(280)
reportFrame:SetHeight(200)
reportFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 50)
reportFrame:SetMovable(true)
reportFrame:EnableMouse(true)
reportFrame:SetClampedToScreen(true)
reportFrame:SetFrameStrata("DIALOG")
SFrames:CreateRoundBackdrop(reportFrame)
-- Title bar
local titleBar = CreateFrame("Frame", nil, reportFrame)
titleBar:SetHeight(22)
titleBar:SetPoint("TOPLEFT", reportFrame, "TOPLEFT", 4, -4)
titleBar:SetPoint("TOPRIGHT", reportFrame, "TOPRIGHT", -4, -4)
titleBar:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 0,
})
if A and A.headerBg then
titleBar:SetBackdropColor(A.headerBg[1], A.headerBg[2], A.headerBg[3], A.headerBg[4] or 0.98)
else
titleBar:SetBackdropColor(0.06, 0.06, 0.08, 0.98)
end
titleBar:EnableMouse(true)
titleBar:RegisterForDrag("LeftButton")
titleBar:SetScript("OnDragStart", function() reportFrame:StartMoving() end)
titleBar:SetScript("OnDragStop", function() reportFrame:StopMovingOrSizing() end)
reportFrame.titleBar = titleBar
local accentHex = (A and A.accentHex) or "ffFF88AA"
local titleText = SFrames:CreateFontString(titleBar, 11, "LEFT")
titleText:SetPoint("LEFT", titleBar, "LEFT", 8, 0)
titleText:SetPoint("RIGHT", titleBar, "RIGHT", -28, 0)
titleText:SetText("|c" .. accentHex .. "Nanami|r DPS - " .. L["Report to Chat"])
local closeBtn = CreateFrame("Button", nil, titleBar)
closeBtn:SetWidth(18)
closeBtn:SetHeight(18)
closeBtn:SetPoint("RIGHT", titleBar, "RIGHT", -3, 0)
local closeIcon = SFrames:CreateIcon(closeBtn, "close", 12)
closeIcon:SetPoint("CENTER", 0, 0)
closeBtn:SetScript("OnClick", function() reportFrame:Hide() end)
closeBtn:SetScript("OnEnter", function() closeIcon:SetVertexColor(1, 0.3, 0.3) end)
closeBtn:SetScript("OnLeave", function() closeIcon:SetVertexColor(1, 1, 1) end)
-- Info line: module + segment
reportFrame.infoText = SFrames:CreateFontString(reportFrame, 9, "LEFT")
reportFrame.infoText:SetPoint("TOPLEFT", titleBar, "BOTTOMLEFT", 8, -8)
reportFrame.infoText:SetPoint("RIGHT", reportFrame, "RIGHT", -12, 0)
if A and A.text then
reportFrame.infoText:SetTextColor(A.text[1], A.text[2], A.text[3])
end
-- Channel section header
local channelLabel = SFrames:CreateFontString(reportFrame, 9, "LEFT")
channelLabel:SetPoint("TOPLEFT", titleBar, "BOTTOMLEFT", 8, -28)
if A and A.sectionTitle then
channelLabel:SetTextColor(A.sectionTitle[1], A.sectionTitle[2], A.sectionTitle[3])
end
channelLabel:SetText(L["Channel"])
-- Channel buttons (4 per row, 2 rows)
reportFrame.channelBtns = {}
local btnW = 60
local btnH = 18
local btnGap = 3
local col, row = 0, 0
for _, ch in ipairs(CHANNEL_LIST) do
local channelId = ch.id
local channelKey = ch.key
local btn = CreateFrame("Button", nil, reportFrame)
btn:SetWidth(btnW)
btn:SetHeight(btnH)
btn:SetPoint("TOPLEFT", titleBar, "BOTTOMLEFT",
8 + col * (btnW + btnGap),
-44 - row * (btnH + btnGap))
btn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
btn.label = SFrames:CreateFontString(btn, 9, "CENTER")
btn.label:SetAllPoints()
btn.label:SetText(L[channelKey])
btn.channelId = channelId
btn:SetScript("OnClick", function()
reportChannel = channelId
UpdateReportChannelButtons()
end)
btn:SetScript("OnEnter", function()
if channelId ~= reportChannel then
if A and A.buttonHoverBg then
this:SetBackdropColor(A.buttonHoverBg[1], A.buttonHoverBg[2], A.buttonHoverBg[3], 0.3)
else
this:SetBackdropColor(0.3, 0.3, 0.3, 0.2)
end
end
end)
btn:SetScript("OnLeave", function()
if channelId ~= reportChannel then
if A and A.buttonBg then
this:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
else
this:SetBackdropColor(0.15, 0.08, 0.12, 0.9)
end
end
end)
table.insert(reportFrame.channelBtns, btn)
col = col + 1
if col >= 4 then
col = 0
row = row + 1
end
end
-- Whisper target row (hidden by default)
reportFrame.whisperRow = CreateFrame("Frame", nil, reportFrame)
reportFrame.whisperRow:SetHeight(22)
reportFrame.whisperRow:SetPoint("TOPLEFT", titleBar, "BOTTOMLEFT", 8, -88)
reportFrame.whisperRow:SetPoint("RIGHT", reportFrame, "RIGHT", -12, 0)
local whisperLabel = SFrames:CreateFontString(reportFrame.whisperRow, 9, "LEFT")
whisperLabel:SetPoint("LEFT", reportFrame.whisperRow, "LEFT", 0, 0)
whisperLabel:SetWidth(40)
if A and A.sectionTitle then
whisperLabel:SetTextColor(A.sectionTitle[1], A.sectionTitle[2], A.sectionTitle[3])
end
whisperLabel:SetText(L["Whisper"] .. ":")
reportFrame.whisperEdit = CreateFrame("EditBox", "NanamiDPSWhisperTarget", reportFrame.whisperRow)
reportFrame.whisperEdit:SetPoint("LEFT", whisperLabel, "RIGHT", 4, 0)
reportFrame.whisperEdit:SetPoint("RIGHT", reportFrame.whisperRow, "RIGHT", 0, 0)
reportFrame.whisperEdit:SetHeight(18)
reportFrame.whisperEdit:SetFontObject(ChatFontNormal)
reportFrame.whisperEdit:SetAutoFocus(false)
reportFrame.whisperEdit:SetMaxLetters(50)
reportFrame.whisperEdit:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
if A and A.checkBg then
reportFrame.whisperEdit:SetBackdropColor(A.checkBg[1], A.checkBg[2], A.checkBg[3], A.checkBg[4] or 0.9)
else
reportFrame.whisperEdit:SetBackdropColor(0.12, 0.06, 0.09, 0.9)
end
if A and A.buttonBorder then
reportFrame.whisperEdit:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
else
reportFrame.whisperEdit:SetBackdropBorderColor(0.4, 0.3, 0.35, 0.8)
end
reportFrame.whisperEdit:SetTextInsets(4, 4, 0, 0)
reportFrame.whisperEdit:SetScript("OnEscapePressed", function() this:ClearFocus() end)
reportFrame.whisperEdit:SetScript("OnEnterPressed", function()
this:ClearFocus()
DoSendReport()
end)
reportFrame.whisperRow:Hide()
-- Lines count row
reportFrame.linesRow = CreateFrame("Frame", nil, reportFrame)
reportFrame.linesRow:SetHeight(22)
reportFrame.linesRow:SetPoint("TOPLEFT", titleBar, "BOTTOMLEFT", 8, -92)
reportFrame.linesRow:SetPoint("RIGHT", reportFrame, "RIGHT", -12, 0)
local linesLabel = SFrames:CreateFontString(reportFrame.linesRow, 9, "LEFT")
linesLabel:SetPoint("LEFT", reportFrame.linesRow, "LEFT", 0, 0)
linesLabel:SetWidth(36)
if A and A.sectionTitle then
linesLabel:SetTextColor(A.sectionTitle[1], A.sectionTitle[2], A.sectionTitle[3])
end
linesLabel:SetText(L["Report Lines"] .. ":")
local countText = SFrames:CreateFontString(reportFrame.linesRow, 10, "CENTER")
countText:SetWidth(30)
local minusBtn = CreateFrame("Button", nil, reportFrame.linesRow)
minusBtn:SetWidth(20)
minusBtn:SetHeight(18)
minusBtn:SetPoint("LEFT", linesLabel, "RIGHT", 4, 0)
minusBtn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
if A and A.buttonBg then
minusBtn:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
else
minusBtn:SetBackdropColor(0.15, 0.08, 0.12, 0.9)
end
if A and A.buttonBorder then
minusBtn:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
else
minusBtn:SetBackdropBorderColor(0.4, 0.3, 0.35, 0.8)
end
local minusLabel = SFrames:CreateFontString(minusBtn, 10, "CENTER")
minusLabel:SetAllPoints()
minusLabel:SetText("-")
countText:SetPoint("LEFT", minusBtn, "RIGHT", 2, 0)
countText:SetText(tostring(reportCount))
if A and A.accent then
countText:SetTextColor(A.accent[1], A.accent[2], A.accent[3])
end
local plusBtn = CreateFrame("Button", nil, reportFrame.linesRow)
plusBtn:SetWidth(20)
plusBtn:SetHeight(18)
plusBtn:SetPoint("LEFT", countText, "RIGHT", 2, 0)
plusBtn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
if A and A.buttonBg then
plusBtn:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
else
plusBtn:SetBackdropColor(0.15, 0.08, 0.12, 0.9)
end
if A and A.buttonBorder then
plusBtn:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
else
plusBtn:SetBackdropBorderColor(0.4, 0.3, 0.35, 0.8)
end
local plusLabel = SFrames:CreateFontString(plusBtn, 10, "CENTER")
plusLabel:SetAllPoints()
plusLabel:SetText("+")
minusBtn:SetScript("OnClick", function()
reportCount = math.max(1, reportCount - 1)
countText:SetText(tostring(reportCount))
end)
plusBtn:SetScript("OnClick", function()
reportCount = math.min(25, reportCount + 1)
countText:SetText(tostring(reportCount))
end)
local function StepperHover(btn)
btn:SetScript("OnEnter", function()
if A and A.buttonHoverBg then
this:SetBackdropColor(A.buttonHoverBg[1], A.buttonHoverBg[2], A.buttonHoverBg[3], 0.3)
else
this:SetBackdropColor(0.3, 0.3, 0.3, 0.2)
end
if A and A.accent then
this:SetBackdropBorderColor(A.accent[1], A.accent[2], A.accent[3], 0.9)
end
end)
btn:SetScript("OnLeave", function()
if A and A.buttonBg then
this:SetBackdropColor(A.buttonBg[1], A.buttonBg[2], A.buttonBg[3], A.buttonBg[4] or 0.9)
else
this:SetBackdropColor(0.15, 0.08, 0.12, 0.9)
end
if A and A.buttonBorder then
this:SetBackdropBorderColor(A.buttonBorder[1], A.buttonBorder[2], A.buttonBorder[3], A.buttonBorder[4] or 0.8)
else
this:SetBackdropBorderColor(0.4, 0.3, 0.35, 0.8)
end
end)
end
StepperHover(minusBtn)
StepperHover(plusBtn)
-- Send Report button
reportFrame.sendBtn = CreateFrame("Button", nil, reportFrame)
reportFrame.sendBtn:SetWidth(160)
reportFrame.sendBtn:SetHeight(24)
reportFrame.sendBtn:SetPoint("TOPLEFT", titleBar, "BOTTOMLEFT", 60, -128)
reportFrame.sendBtn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
if A and A.accent then
reportFrame.sendBtn:SetBackdropColor(A.accent[1] * 0.25, A.accent[2] * 0.25, A.accent[3] * 0.25, 0.95)
reportFrame.sendBtn:SetBackdropBorderColor(A.accent[1], A.accent[2], A.accent[3], 0.8)
else
reportFrame.sendBtn:SetBackdropColor(0.2, 0.1, 0.15, 0.95)
reportFrame.sendBtn:SetBackdropBorderColor(1, 0.53, 0.67, 0.8)
end
local sendLabel = SFrames:CreateFontString(reportFrame.sendBtn, 10, "CENTER")
sendLabel:SetAllPoints()
sendLabel:SetText(L["Send Report"])
if A and A.accent then
sendLabel:SetTextColor(A.accent[1], A.accent[2], A.accent[3])
end
reportFrame.sendBtn:SetScript("OnClick", function()
DoSendReport()
end)
reportFrame.sendBtn:SetScript("OnEnter", function()
if A and A.accent then
this:SetBackdropColor(A.accent[1] * 0.4, A.accent[2] * 0.4, A.accent[3] * 0.4, 0.95)
else
this:SetBackdropColor(0.35, 0.15, 0.25, 0.95)
end
end)
reportFrame.sendBtn:SetScript("OnLeave", function()
if A and A.accent then
this:SetBackdropColor(A.accent[1] * 0.25, A.accent[2] * 0.25, A.accent[3] * 0.25, 0.95)
else
this:SetBackdropColor(0.2, 0.1, 0.15, 0.95)
end
end)
UpdateReportChannelButtons()
reportFrame:Hide()
return reportFrame
end
local function ShowReportPanel(anchorFrame)
local frame = EnsureReportFrame()
frame:ClearAllPoints()
if anchorFrame and anchorFrame:GetRight() and (anchorFrame:GetRight() + 290) < GetScreenWidth() then
frame:SetPoint("TOPLEFT", anchorFrame, "TOPRIGHT", 4, 0)
else
frame:SetPoint("TOPRIGHT", anchorFrame, "TOPLEFT", -4, 0)
end
UpdateReportChannelButtons()
if frame:IsShown() then
frame:Hide()
else
frame:Show()
end
end
function Window:ShowReport(sourceWindow)
local frame = EnsureReportFrame()
reportSourceWin = sourceWindow
local mod = NanamiDPS.modules[sourceWindow.activeModuleName]
local modName = mod and mod:GetName() or sourceWindow.activeModuleName
local segName = GetSegmentName(sourceWindow)
local accentHex = (SFrames.ActiveTheme and SFrames.ActiveTheme.accentHex) or "ffFF88AA"
frame.infoText:SetText("|c" .. accentHex .. modName .. "|r - " .. segName)
reportHeaderFn = function()
return "Nanami DPS - " .. segName .. " " .. modName .. ":"
end
reportLinesFn = function(count)
local seg = DataStore:GetSegment(sourceWindow.segmentIndex or 1)
if mod and mod.GetReportLines then
return mod:GetReportLines(seg, count)
end
return {}
end
ShowReportPanel(sourceWindow)
end
function Window:ShowReportCustom(anchorFrame, infoStr, headerFn, linesFn)
local frame = EnsureReportFrame()
reportSourceWin = nil
reportHeaderFn = headerFn
reportLinesFn = linesFn
frame.infoText:SetText(infoStr)
ShowReportPanel(anchorFrame)
end