diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..68b3147 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +All notable changes to OctoLatency are documented in this file. + +## [Unreleased] +### Added +- `/octolat copyerrors` and a minimap button, both opening a copy-errors window: a multi-line `EditBox` pre-filled with the error log and auto-highlighted, so the text can be selected and copied with Ctrl+C (vanilla WoW's chat frame doesn't support text selection). +- Minimap button also right-click toggles the latency frame, and can be dragged to reposition around the minimap (position saved to `OctoLatencyDB.minimapAngle`). +- Colored addon title in the character-select AddOns list: `OctoLatency` now renders as a per-letter gradient from dark to light green (`## Title:` in `OctoLatency.toc`). + +### Fixed +- Tooltip (`OnEnter`) no longer errors with `attempt to concatenate local 'worldLatency' (a nil value)`. `GetNetStats()` can return `nil` for latency/bandwidth briefly after login or a loading screen; the tooltip now falls back to `0` for `bandwidth`, `homeLatency`, and `worldLatency`, matching the fallback already used by the status text update. + +## [1.0] +### Added +- Core addon: draggable frame showing world latency (falling back to home latency) with color-coded text (green < 150ms, yellow 150–350ms, red > 350ms), updated every 2 seconds. +- Tooltip on hover showing home latency, world latency, and bandwidth. +- `/octolat errors` slash command to view the last 20 logged errors in-game. +- `/octolat clearerrors` slash command to clear the error log. +- Error logging: tooltip/update/login handlers run through `pcall`, with failures recorded to `OctoLatencyDB.errorLog` (persisted in SavedVariables) and echoed to the chat frame instead of raising a Lua error popup. + +### Fixed +- Stuck drag state when the mouse button is released off-frame: dragging now also stops on `OnHide`, not just `OnMouseUp`. + +### Removed +- Dead `VARIABLES_LOADED` event registration (no handler was ever attached to it). diff --git a/OctoLatency.lua b/OctoLatency.lua index 931691c..8beed48 100644 --- a/OctoLatency.lua +++ b/OctoLatency.lua @@ -52,6 +52,9 @@ function OctoLatency_OnLoad(self) GameTooltip:SetOwner(this, "ANCHOR_TOP") GameTooltip:AddLine("OctoLatency", 0, 1, 1) local bandwidth, latency, homeLatency, worldLatency = GetNetStats() + bandwidth = bandwidth or 0 + homeLatency = homeLatency or 0 + worldLatency = worldLatency or 0 GameTooltip:AddLine("Home Latency: " .. homeLatency .. " ms", 1, 1, 1) GameTooltip:AddLine("World Latency: " .. worldLatency .. " ms", 1, 1, 1) GameTooltip:AddLine("Bandwidth: " .. string.format("%.2f", bandwidth) .. " KB/s", 1, 1, 1) @@ -114,7 +117,155 @@ function OctoLatency_SavePosition() OctoLatencyDB.yOfs = yOfs end --- Load saved position if available on PLAYER_LOGIN or VARIABLES_LOADED +-------------------------------------------------------------------------------- +-- Copy Errors window: vanilla WoW chat text can't be selected or copied, so +-- this shows the log in a multi-line EditBox that's auto-highlighted for Ctrl+C. +-------------------------------------------------------------------------------- +local copyFrame, copyEditBox + +local function OctoLatency_BuildErrorText() + local db = OctoLatency_EnsureDB() + if (table.getn(db.errorLog) == 0) then + return "No errors logged." + end + local lines = {} + for i, entry in ipairs(db.errorLog) do + table.insert(lines, "[" .. entry.time .. "] " .. entry.context .. ": " .. entry.message) + end + return table.concat(lines, "\n") +end + +local function OctoLatency_CreateCopyFrame() + local f = CreateFrame("Frame", "OctoLatencyCopyFrame", UIParent) + f:SetFrameStrata("DIALOG") + f:SetWidth(420) + f:SetHeight(260) + f:SetPoint("CENTER") + f:SetBackdrop({ + bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", + edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", + tile = true, tileSize = 32, edgeSize = 32, + insets = { left = 11, right = 12, top = 12, bottom = 11 }, + }) + f:EnableMouse(true) + f:SetMovable(true) + f:RegisterForDrag("LeftButton") + f:SetScript("OnDragStart", function() this:StartMoving() end) + f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end) + f:Hide() + + local title = f:CreateFontString(nil, "ARTWORK", "GameFontNormal") + title:SetPoint("TOP", f, "TOP", 0, -16) + title:SetText("OctoLatency Errors (Ctrl+C to copy, Esc to close)") + + local scroll = CreateFrame("ScrollFrame", "OctoLatencyCopyScroll", f, "UIPanelScrollFrameTemplate") + scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -40) + scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -34, 44) + + local editBox = CreateFrame("EditBox", "OctoLatencyCopyEditBox", scroll) + editBox:SetMultiLine(true) + editBox:SetFontObject(ChatFontNormal) + editBox:SetWidth(360) + editBox:SetAutoFocus(false) + editBox:SetScript("OnEscapePressed", function() copyFrame:Hide() end) + scroll:SetScrollChild(editBox) + copyEditBox = editBox + + local closeBtn = CreateFrame("Button", nil, f, "UIPanelCloseButton") + closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -4) + closeBtn:SetScript("OnClick", function() f:Hide() end) + + copyFrame = f +end + +function OctoLatency_ShowCopyErrors() + if (not copyFrame) then + OctoLatency_CreateCopyFrame() + end + copyEditBox:SetText(OctoLatency_BuildErrorText()) + copyFrame:Show() + copyEditBox:SetFocus() + copyEditBox:HighlightText() +end + +-------------------------------------------------------------------------------- +-- Minimap button: left-click opens the copy-errors window, right-click toggles +-- the latency frame, drag to reposition around the minimap. +-------------------------------------------------------------------------------- +local function OctoLatency_UpdateMinimapButtonPosition(button) + local db = OctoLatency_EnsureDB() + local angle = db.minimapAngle or 220 + local rad = math.rad(angle) + local radius = 80 + button:ClearAllPoints() + button:SetPoint("CENTER", Minimap, "CENTER", radius * math.cos(rad), radius * math.sin(rad)) +end + +local function OctoLatency_CreateMinimapButton() + local button = CreateFrame("Button", "OctoLatencyMinimapButton", Minimap) + button:SetWidth(31) + button:SetHeight(31) + button:SetFrameStrata("MEDIUM") + button:SetFrameLevel(8) + button:RegisterForClicks("LeftButtonUp", "RightButtonUp") + button:RegisterForDrag("LeftButton") + + local icon = button:CreateTexture(nil, "BACKGROUND") + icon:SetWidth(20) + icon:SetHeight(20) + icon:SetPoint("CENTER", 0, 0) + icon:SetTexture("Interface\\Icons\\INV_Misc_PocketWatch_01") + + local overlay = button:CreateTexture(nil, "OVERLAY") + overlay:SetWidth(53) + overlay:SetHeight(53) + overlay:SetPoint("TOPLEFT", 0, 0) + overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") + + button:SetScript("OnClick", function() + OctoLatency_SafeCall("MinimapButtonClick", function() + if (arg1 == "RightButton") then + if (OctoLatencyFrame:IsShown()) then + OctoLatencyFrame:Hide() + else + OctoLatencyFrame:Show() + end + else + OctoLatency_ShowCopyErrors() + end + end) + end) + + button:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_LEFT") + GameTooltip:AddLine("OctoLatency", 0, 1, 1) + GameTooltip:AddLine("Left-click: copy error log", 1, 1, 1) + GameTooltip:AddLine("Right-click: toggle latency frame", 1, 1, 1) + GameTooltip:AddLine("Drag: move this button", 0.7, 0.7, 0.7) + GameTooltip:Show() + end) + button:SetScript("OnLeave", function() GameTooltip:Hide() end) + + button:SetScript("OnDragStart", function() + this:SetScript("OnUpdate", function() + local mx, my = GetCursorPosition() + local scale = Minimap:GetEffectiveScale() + mx, my = mx / scale, my / scale + local cx, cy = Minimap:GetCenter() + local db = OctoLatency_EnsureDB() + db.minimapAngle = math.deg(math.atan2(my - cy, mx - cx)) + OctoLatency_UpdateMinimapButtonPosition(this) + end) + end) + button:SetScript("OnDragStop", function() + this:SetScript("OnUpdate", nil) + end) + + OctoLatency_UpdateMinimapButtonPosition(button) + return button +end + +-- Load saved position and create the minimap button on PLAYER_LOGIN local loadFrame = CreateFrame("Frame") loadFrame:RegisterEvent("PLAYER_LOGIN") loadFrame:SetScript("OnEvent", function() @@ -123,11 +274,12 @@ loadFrame:SetScript("OnEvent", function() OctoLatencyFrame:ClearAllPoints() OctoLatencyFrame:SetPoint(OctoLatencyDB.point, UIParent, OctoLatencyDB.relativePoint, OctoLatencyDB.xOfs, OctoLatencyDB.yOfs) end + OctoLatency_CreateMinimapButton() end) end) -------------------------------------------------------------------------------- --- Slash command: /octolat errors | clearerrors +-- Slash command: /octolat errors | copyerrors | clearerrors -------------------------------------------------------------------------------- SLASH_OCTOLATENCY1 = "/octolat" SlashCmdList["OCTOLATENCY"] = function(msg) @@ -143,10 +295,12 @@ SlashCmdList["OCTOLATENCY"] = function(msg) for i, entry in ipairs(db.errorLog) do DEFAULT_CHAT_FRAME:AddMessage("[" .. entry.time .. "] " .. entry.context .. ": " .. entry.message) end + elseif (msg == "copyerrors") then + OctoLatency_ShowCopyErrors() elseif (msg == "clearerrors") then db.errorLog = {} DEFAULT_CHAT_FRAME:AddMessage("OctoLatency: error log cleared.") else - DEFAULT_CHAT_FRAME:AddMessage("OctoLatency commands: /octolat errors, /octolat clearerrors") + DEFAULT_CHAT_FRAME:AddMessage("OctoLatency commands: /octolat errors, /octolat copyerrors, /octolat clearerrors") end end diff --git a/OctoLatency.toc b/OctoLatency.toc index a64bab0..5206079 100644 --- a/OctoLatency.toc +++ b/OctoLatency.toc @@ -1,5 +1,5 @@ ## Interface: 11200 -## Title: OctoLatency +## Title: |cff003c00O|cff005000c|cff006300t|cff007700o|cff008a00L|cff009e00a|cff00b100t|cff00c500e|cff00d800n|cff00ec00c|cff00ff00y|r ## Notes: Lightweight latency monitor with color-coding for Vanilla WoW (1.12.1) ## Author: Palladinpala ## Version: 1.0 diff --git a/README.md b/README.md index 30f79ff..f59b8c1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,16 @@ you to report: - `/octolat errors` — print the last 20 logged errors (with timestamps) to chat. +- `/octolat copyerrors` — open a copy-paste window with the same log, since + vanilla WoW's chat frame doesn't let you select/copy its text. The window + opens with the text already highlighted, so you can just press Ctrl+C. - `/octolat clearerrors` — clear the error log. +There's also a minimap button for the same purpose without typing a command: + +- **Left-click** — open the copy-errors window. +- **Right-click** — toggle the latency frame on/off. +- **Drag** — reposition the button around the minimap. + The log is also saved to your `SavedVariables` file, so it survives logout/relog.