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 function ShowDropdown(parent, items, onClick, anchorPoint, relTo, relPoint, xOff, yOff) 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) dd:Hide() 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 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) -- Report button local reportBtn = CreateFrame("Button", nil, frame.titleBar) reportBtn:SetWidth(16) reportBtn:SetHeight(16) reportBtn:SetPoint("RIGHT", resetBtn, "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: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: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) --------------------------------------------------------------- -- 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, } 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 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: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() local win = Window:Create(1) activeWindows[1] = win NanamiDPS.windows = activeWindows NanamiDPS:RegisterCallback("refresh", "WindowRefresh", function() for _, w in pairs(activeWindows) do if w then w.needsRefresh = true end end end) Window:LoadPosition(win) local cfg = NanamiDPS.config or {} if cfg.visible ~= false then win:Show() else win:Hide() end Window:RefreshWindow(win, true) 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 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