From fc5a8aa3613ed163e3f7f81e12116ef6f31ae8b6 Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Fri, 10 Apr 2026 21:30:31 +0200 Subject: [PATCH] initial commit --- Changelog.lua | 36 + Core/AceWindow.lua | 176 + Core/DebugWindow.lua | 486 ++ Core/DevTools.lua | 227 + Core/DevToolsWindow.lua | 254 + Core/VersionNoticeWindow.lua | 103 + Embeds.xml | 44 + HailMaryGuildTools.lua | 5547 +++++++++++++++++ HailMaryGuildTools.toc | 59 + HailMaryGuildToolsOptions.lua | 2063 ++++++ Libs/AceAddon-3.0/AceAddon-3.0.lua | 649 ++ Libs/AceAddon-3.0/AceAddon-3.0.xml | 4 + Libs/AceComm-3.0/AceComm-3.0.lua | 301 + Libs/AceComm-3.0/AceComm-3.0.xml | 5 + Libs/AceComm-3.0/ChatThrottleLib.lua | 701 +++ Libs/AceConfig-3.0/AceConfig-3.0.lua | 58 + Libs/AceConfig-3.0/AceConfig-3.0.xml | 8 + .../AceConfigCmd-3.0/AceConfigCmd-3.0.lua | 787 +++ .../AceConfigCmd-3.0/AceConfigCmd-3.0.xml | 4 + .../AceConfigDialog-3.0.lua | 2045 ++++++ .../AceConfigDialog-3.0.xml | 4 + .../AceConfigRegistry-3.0.lua | 373 ++ .../AceConfigRegistry-3.0.xml | 4 + Libs/AceConsole-3.0/AceConsole-3.0.lua | 246 + Libs/AceConsole-3.0/AceConsole-3.0.xml | 4 + Libs/AceDB-3.0/AceDB-3.0.lua | 797 +++ Libs/AceDB-3.0/AceDB-3.0.xml | 4 + Libs/AceDBOptions-3.0/AceDBOptions-3.0.lua | 456 ++ Libs/AceDBOptions-3.0/AceDBOptions-3.0.xml | 4 + Libs/AceEvent-3.0/AceEvent-3.0.lua | 126 + Libs/AceEvent-3.0/AceEvent-3.0.xml | 4 + Libs/AceGUI-3.0/AceGUI-3.0.lua | 1020 +++ Libs/AceGUI-3.0/AceGUI-3.0.xml | 28 + .../AceGUIContainer-BlizOptionsGroup.lua | 143 + .../widgets/AceGUIContainer-DropDownGroup.lua | 157 + .../widgets/AceGUIContainer-Frame.lua | 318 + .../widgets/AceGUIContainer-InlineGroup.lua | 103 + .../widgets/AceGUIContainer-ScrollFrame.lua | 215 + .../widgets/AceGUIContainer-SimpleGroup.lua | 69 + .../widgets/AceGUIContainer-TabGroup.lua | 535 ++ .../widgets/AceGUIContainer-TreeGroup.lua | 719 +++ .../widgets/AceGUIContainer-Window.lua | 336 + .../widgets/AceGUIWidget-Button.lua | 103 + .../widgets/AceGUIWidget-CheckBox.lua | 292 + .../widgets/AceGUIWidget-ColorPicker.lua | 230 + .../widgets/AceGUIWidget-DropDown-Items.lua | 471 ++ .../widgets/AceGUIWidget-DropDown.lua | 732 +++ .../widgets/AceGUIWidget-EditBox.lua | 267 + .../widgets/AceGUIWidget-Heading.lua | 78 + Libs/AceGUI-3.0/widgets/AceGUIWidget-Icon.lua | 140 + .../widgets/AceGUIWidget-InteractiveLabel.lua | 94 + .../widgets/AceGUIWidget-Keybinding.lua | 251 + .../AceGUI-3.0/widgets/AceGUIWidget-Label.lua | 179 + .../widgets/AceGUIWidget-MultiLineEditBox.lua | 377 ++ .../widgets/AceGUIWidget-Slider.lua | 280 + Libs/AceLocale-3.0/AceLocale-3.0.lua | 133 + Libs/AceLocale-3.0/AceLocale-3.0.xml | 4 + Libs/AceSerializer-3.0/AceSerializer-3.0.lua | 287 + Libs/AceSerializer-3.0/AceSerializer-3.0.xml | 4 + Libs/AceTimer-3.0/AceTimer-3.0.lua | 278 + Libs/AceTimer-3.0/AceTimer-3.0.xml | 4 + .../CallbackHandler-1.0.lua | 202 + .../CallbackHandler-1.0.xml | 4 + Libs/LibGroupInSpecT-1.1/CHANGES.txt | 23 + .../CallbackHandler-1.0.lua | 240 + .../LibGroupInSpecT-1.1.lua | 1067 ++++ .../LibGroupInSpecT-1.1.toc | 10 + Libs/LibGroupInSpecT-1.1/LibStub/LibStub.lua | 51 + Libs/LibGroupInSpecT-1.1/lib.xml | 6 + Libs/LibSharedMedia-3.0/CHANGES.txt | 16 + .../CallbackHandler-1.0.lua | 202 + .../LibSharedMedia-3.0/LibSharedMedia-3.0.toc | 17 + .../LibSharedMedia-3.0/LibSharedMedia-3.0.lua | 324 + .../LibSharedMedia-3.0/lib.xml | 4 + Libs/LibSharedMedia-3.0/LibStub/LibStub.lua | 51 + Libs/LibSharedMedia-3.0/lib.xml | 5 + Libs/LibStub/LibStub.lua | 30 + Locales/Locales.xml | 4 + Locales/deDE.lua | 483 ++ Locales/enUS.lua | 483 ++ Media/HailMaryIcon.png | Bin 0 -> 2648 bytes Media/HailMaryLogo.png | Bin 0 -> 127890 bytes .../BuffEndingAnnouncer.lua | 472 ++ .../BuffEndingAnnouncerOptions.lua | 409 ++ Modules/MapOverlay/MapOverlay.lua | 1193 ++++ Modules/MapOverlay/MapOverlay.xml | 11 + Modules/MapOverlay/MapOverlayIconConfig.lua | 37 + Modules/MapOverlay/MapOverlayOptions.lua | 554 ++ Modules/MapOverlay/Media/DefaultIcon.png | Bin 0 -> 2648 bytes Modules/RaidTimeline/RaidTimeline.lua | 2814 +++++++++ Modules/RaidTimeline/RaidTimelineBigWigs.lua | 202 + .../RaidTimelineBossAbilityData.lua | 52 + Modules/RaidTimeline/RaidTimelineDBM.lua | 8 + Modules/RaidTimeline/RaidTimelineOptions.lua | 799 +++ Modules/Tracker/Frame.lua | 1231 ++++ .../GroupCooldownSpellDatabase.lua | 391 ++ .../GroupCooldownTracker.lua | 692 ++ Modules/Tracker/GroupTrackerFrames.lua | 247 + .../InterruptSpellDatabase.lua | 154 + .../InterruptTracker/InterruptTracker.lua | 21 + Modules/Tracker/NormalTrackerFrames.lua | 68 + .../RaidCooldownSpellDatabase.lua | 128 + .../RaidcooldownTracker.lua | 21 + Modules/Tracker/SingleFrameTrackerBase.lua | 305 + Modules/Tracker/SpellDatabase.lua | 1104 ++++ Modules/Tracker/TrackerManager.lua | 803 +++ Modules/Tracker/TrackerOptions.lua | 2175 +++++++ readme.md | 29 + 108 files changed, 40568 insertions(+) create mode 100644 Changelog.lua create mode 100644 Core/AceWindow.lua create mode 100644 Core/DebugWindow.lua create mode 100644 Core/DevTools.lua create mode 100644 Core/DevToolsWindow.lua create mode 100644 Core/VersionNoticeWindow.lua create mode 100644 Embeds.xml create mode 100644 HailMaryGuildTools.lua create mode 100644 HailMaryGuildTools.toc create mode 100644 HailMaryGuildToolsOptions.lua create mode 100644 Libs/AceAddon-3.0/AceAddon-3.0.lua create mode 100644 Libs/AceAddon-3.0/AceAddon-3.0.xml create mode 100644 Libs/AceComm-3.0/AceComm-3.0.lua create mode 100644 Libs/AceComm-3.0/AceComm-3.0.xml create mode 100644 Libs/AceComm-3.0/ChatThrottleLib.lua create mode 100644 Libs/AceConfig-3.0/AceConfig-3.0.lua create mode 100644 Libs/AceConfig-3.0/AceConfig-3.0.xml create mode 100644 Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua create mode 100644 Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml create mode 100644 Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua create mode 100644 Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml create mode 100644 Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua create mode 100644 Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml create mode 100644 Libs/AceConsole-3.0/AceConsole-3.0.lua create mode 100644 Libs/AceConsole-3.0/AceConsole-3.0.xml create mode 100644 Libs/AceDB-3.0/AceDB-3.0.lua create mode 100644 Libs/AceDB-3.0/AceDB-3.0.xml create mode 100644 Libs/AceDBOptions-3.0/AceDBOptions-3.0.lua create mode 100644 Libs/AceDBOptions-3.0/AceDBOptions-3.0.xml create mode 100644 Libs/AceEvent-3.0/AceEvent-3.0.lua create mode 100644 Libs/AceEvent-3.0/AceEvent-3.0.xml create mode 100644 Libs/AceGUI-3.0/AceGUI-3.0.lua create mode 100644 Libs/AceGUI-3.0/AceGUI-3.0.xml create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-BlizOptionsGroup.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-DropDownGroup.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-InlineGroup.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-SimpleGroup.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-Heading.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-Icon.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-InteractiveLabel.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-Label.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-MultiLineEditBox.lua create mode 100644 Libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua create mode 100644 Libs/AceLocale-3.0/AceLocale-3.0.lua create mode 100644 Libs/AceLocale-3.0/AceLocale-3.0.xml create mode 100644 Libs/AceSerializer-3.0/AceSerializer-3.0.lua create mode 100644 Libs/AceSerializer-3.0/AceSerializer-3.0.xml create mode 100644 Libs/AceTimer-3.0/AceTimer-3.0.lua create mode 100644 Libs/AceTimer-3.0/AceTimer-3.0.xml create mode 100644 Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua create mode 100644 Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml create mode 100644 Libs/LibGroupInSpecT-1.1/CHANGES.txt create mode 100644 Libs/LibGroupInSpecT-1.1/CallbackHandler-1.0/CallbackHandler-1.0.lua create mode 100644 Libs/LibGroupInSpecT-1.1/LibGroupInSpecT-1.1.lua create mode 100644 Libs/LibGroupInSpecT-1.1/LibGroupInSpecT-1.1.toc create mode 100644 Libs/LibGroupInSpecT-1.1/LibStub/LibStub.lua create mode 100644 Libs/LibGroupInSpecT-1.1/lib.xml create mode 100644 Libs/LibSharedMedia-3.0/CHANGES.txt create mode 100644 Libs/LibSharedMedia-3.0/CallbackHandler-1.0/CallbackHandler-1.0.lua create mode 100644 Libs/LibSharedMedia-3.0/LibSharedMedia-3.0.toc create mode 100644 Libs/LibSharedMedia-3.0/LibSharedMedia-3.0/LibSharedMedia-3.0.lua create mode 100644 Libs/LibSharedMedia-3.0/LibSharedMedia-3.0/lib.xml create mode 100644 Libs/LibSharedMedia-3.0/LibStub/LibStub.lua create mode 100644 Libs/LibSharedMedia-3.0/lib.xml create mode 100644 Libs/LibStub/LibStub.lua create mode 100644 Locales/Locales.xml create mode 100644 Locales/deDE.lua create mode 100644 Locales/enUS.lua create mode 100644 Media/HailMaryIcon.png create mode 100644 Media/HailMaryLogo.png create mode 100644 Modules/BuffEndingAnnouncer/BuffEndingAnnouncer.lua create mode 100644 Modules/BuffEndingAnnouncer/BuffEndingAnnouncerOptions.lua create mode 100644 Modules/MapOverlay/MapOverlay.lua create mode 100644 Modules/MapOverlay/MapOverlay.xml create mode 100644 Modules/MapOverlay/MapOverlayIconConfig.lua create mode 100644 Modules/MapOverlay/MapOverlayOptions.lua create mode 100644 Modules/MapOverlay/Media/DefaultIcon.png create mode 100644 Modules/RaidTimeline/RaidTimeline.lua create mode 100644 Modules/RaidTimeline/RaidTimelineBigWigs.lua create mode 100644 Modules/RaidTimeline/RaidTimelineBossAbilityData.lua create mode 100644 Modules/RaidTimeline/RaidTimelineDBM.lua create mode 100644 Modules/RaidTimeline/RaidTimelineOptions.lua create mode 100644 Modules/Tracker/Frame.lua create mode 100644 Modules/Tracker/GroupCooldownTracker/GroupCooldownSpellDatabase.lua create mode 100644 Modules/Tracker/GroupCooldownTracker/GroupCooldownTracker.lua create mode 100644 Modules/Tracker/GroupTrackerFrames.lua create mode 100644 Modules/Tracker/InterruptTracker/InterruptSpellDatabase.lua create mode 100644 Modules/Tracker/InterruptTracker/InterruptTracker.lua create mode 100644 Modules/Tracker/NormalTrackerFrames.lua create mode 100644 Modules/Tracker/RaidcooldownTracker/RaidCooldownSpellDatabase.lua create mode 100644 Modules/Tracker/RaidcooldownTracker/RaidcooldownTracker.lua create mode 100644 Modules/Tracker/SingleFrameTrackerBase.lua create mode 100644 Modules/Tracker/SpellDatabase.lua create mode 100644 Modules/Tracker/TrackerManager.lua create mode 100644 Modules/Tracker/TrackerOptions.lua create mode 100644 readme.md diff --git a/Changelog.lua b/Changelog.lua new file mode 100644 index 0000000..4601451 --- /dev/null +++ b/Changelog.lua @@ -0,0 +1,36 @@ +HMGT_Changelog = { + releases = { + enUS = { + { + version = "2.0", + entries = { + "The tracker system was rebuilt around individual tracker configs instead of the previous fixed tracker modules.", + "You can now create custom trackers and assign your own spell category combinations to each tracker.", + "Party-attached demo/test previews now only create tracker frames when matching party frames are actually available.", + "Party-frame attachment now aligns tracker content against the visible bar/icon area instead of the outer frame bounds, so the default Y offset lines up cleanly again.", + "Upward bar growth was reworked so bars anchor above the tracker title and grow upward consistently in normal layouts and party-attached layouts.", + "Tracker category selections now persist correctly across /reload and are no longer repopulated from default tracker entries.", + "Removing a tracker now asks for confirmation in the options UI before deleting it.", + "Use Class Colours now correctly keeps progress bars in class color, while the fallback cooldown coloring now follows red, then yellow, then green thresholds.", + "Bar rows now use isolated status bar textures so color changes no longer bleed from one active bar into another.", + }, + }, + }, + deDE = { + { + version = "2.0", + entries = { + "Das Tracker-System wurde auf einzelne Tracker-Konfigurationen umgebaut statt auf die bisherigen fest verdrahteten Tracker-Module.", + "Es koennen jetzt eigene Custom-Tracker angelegt und mit frei waehlbaren Zauber-Kategorie-Kombinationen konfiguriert werden.", + "Demo-/Test-Vorschauen mit Party-Attach erzeugen Tracker-Frames jetzt nur noch dann, wenn passende Party-Frames auch wirklich verfuegbar sind.", + "Das Party-Attach richtet den sichtbaren Tracker-Inhalt jetzt am echten Bar-/Icon-Bereich statt an den aeusseren Frame-Grenzen aus, sodass der Standard-Y-Offset wieder sauber passt.", + "Aufwaerts wachsende Bars wurden ueberarbeitet: sie werden jetzt oberhalb des Tracker-Titels verankert und wachsen in normalen Layouts wie auch bei Party-Attach konsistent nach oben.", + "Tracker-Kategorien bleiben nach /reload jetzt korrekt gespeichert und werden nicht mehr aus Default-Tracker-Eintraegen wieder aufgefuellt.", + "Das Entfernen eines Trackers fragt in den Optionen jetzt erst nach einer Bestaetigung.", + "Use Class Colours faerbt Progress-Bars jetzt korrekt in Klassenfarben; ohne diese Option nutzt die Restzeit-Faerbung jetzt rot, dann gelb, dann gruen.", + "Bar-Zeilen verwenden jetzt eigene StatusBar-Texturen, damit Farbwechsel nicht mehr von einer aktiven Bar auf eine andere ueberlaufen.", + }, + }, + }, + }, +} diff --git a/Core/AceWindow.lua b/Core/AceWindow.lua new file mode 100644 index 0000000..d422f6f --- /dev/null +++ b/Core/AceWindow.lua @@ -0,0 +1,176 @@ +local ADDON_NAME = "HailMaryGuildTools" +local HMGT = _G[ADDON_NAME] +if not HMGT then return end + +local AceGUI = LibStub("AceGUI-3.0", true) +if not AceGUI then return end + +local WindowPrototype = {} + +local function ResolveFrame(target) + if not target then + return nil + end + if type(target) == "table" and target.frame then + return target.frame + end + return target +end + +function WindowPrototype:GetContent() + return self.content +end + +function WindowPrototype:SetTitle(text) + if self.widget and self.widget.SetTitle then + self.widget:SetTitle(tostring(text or "")) + end +end + +function WindowPrototype:SetStatusText(text) + if self.widget and self.widget.SetStatusText then + self.widget:SetStatusText(tostring(text or "")) + end +end + +function WindowPrototype:Show() + if self.widget and self.widget.Show then + self.widget:Show() + elseif self.frame then + self.frame:Show() + end +end + +function WindowPrototype:Hide() + if self.widget and self.widget.Hide then + self.widget:Hide() + elseif self.frame then + self.frame:Hide() + end +end + +function WindowPrototype:Raise() + if self.frame and self.frame.Raise then + self.frame:Raise() + end +end + +function WindowPrototype:IsShown() + return self.frame and self.frame:IsShown() or false +end + +function WindowPrototype:RegisterMinimizeTarget(target) + self.minimizeTargets = self.minimizeTargets or {} + self.minimizeTargets[#self.minimizeTargets + 1] = target +end + +function WindowPrototype:SetMinimized(minimized) + minimized = minimized and true or false + if not self.minimizable then + minimized = false + end + + self.statusTable = self.statusTable or {} + self.statusTable.minimized = minimized + if minimized then + self.statusTable.restoreHeight = self.statusTable.height or (self.frame and self.frame:GetHeight()) or self.height or 360 + end + + local targetHeight = minimized + and (self.minimizedHeight or 64) + or (self.statusTable.restoreHeight or self.statusTable.height or self.height or 360) + + if self.widget and self.widget.EnableResize then + self.widget:EnableResize(not minimized) + self.widget:SetHeight(targetHeight) + elseif self.frame then + self.frame:SetHeight(targetHeight) + end + + if self.minimizeButton and self.minimizeButton.SetText then + self.minimizeButton:SetText(minimized and "+" or "-") + end + + for _, target in ipairs(self.minimizeTargets or {}) do + local frame = ResolveFrame(target) + if frame and frame.SetShown then + frame:SetShown(not minimized) + end + end +end + +function WindowPrototype:ToggleMinimized() + self:SetMinimized(not (self.statusTable and self.statusTable.minimized)) +end + +function HMGT:CreateAceWindow(key, options) + self._aceWindows = self._aceWindows or {} + if self._aceWindows[key] then + return self._aceWindows[key] + end + + if not AceGUI then + return nil + end + + options = options or {} + local statusTable = options.statusTable or {} + local widget = AceGUI:Create("Frame") + widget:SetTitle(tostring(options.title or "")) + widget:SetStatusText(tostring(options.statusText or "")) + widget:SetStatusTable(statusTable) + widget:SetWidth(tonumber(options.width) or 800) + widget:SetHeight(tonumber(options.height) or 360) + widget:EnableResize(options.resizable ~= false) + widget.frame:SetClampedToScreen(true) + widget.frame:SetToplevel(true) + widget.frame:SetFrameStrata(options.strata or "FULLSCREEN_DIALOG") + widget:Hide() + widget:SetCallback("OnClose", function() + widget:Hide() + end) + + local window = setmetatable({ + key = key, + widget = widget, + frame = widget.frame, + content = widget.content, + statusTable = statusTable, + width = tonumber(options.width) or 800, + height = tonumber(options.height) or 360, + minimizable = options.minimizable == true, + minimizedHeight = tonumber(options.minimizedHeight) or 64, + minimizeTargets = {}, + }, { __index = WindowPrototype }) + + if options.backgroundTexture then + local texture = window.content:CreateTexture(nil, "BACKGROUND") + texture:SetPoint("CENTER", window.content, "CENTER", tonumber(options.backgroundOffsetX) or 0, tonumber(options.backgroundOffsetY) or 0) + texture:SetSize(tonumber(options.backgroundWidth) or 220, tonumber(options.backgroundHeight) or 120) + texture:SetTexture(tostring(options.backgroundTexture)) + texture:SetAlpha(tonumber(options.backgroundAlpha) or 0.12) + window.backgroundTexture = texture + end + + if window.minimizable then + local minimizeButton = AceGUI:Create("Button") + minimizeButton:SetText((statusTable and statusTable.minimized) and "+" or "-") + minimizeButton:SetWidth(24) + minimizeButton:SetCallback("OnClick", function() + window:ToggleMinimized() + end) + minimizeButton.frame:SetParent(window.frame) + minimizeButton.frame:ClearAllPoints() + minimizeButton.frame:SetPoint("TOPRIGHT", window.frame, "TOPRIGHT", -34, -4) + minimizeButton.frame:SetHeight(20) + minimizeButton.frame:Show() + window.minimizeButton = minimizeButton + end + + self._aceWindows[key] = window + if options.onCreate then + options.onCreate(window, window.content, widget) + end + window:SetMinimized(statusTable and statusTable.minimized) + return window +end diff --git a/Core/DebugWindow.lua b/Core/DebugWindow.lua new file mode 100644 index 0000000..59e7a77 --- /dev/null +++ b/Core/DebugWindow.lua @@ -0,0 +1,486 @@ +local ADDON_NAME = "HailMaryGuildTools" +local HMGT = _G[ADDON_NAME] +if not HMGT then return end + +local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME) +local AceGUI = LibStub("AceGUI-3.0", true) + +local function GetOrderedDebugLevels() + return { "error", "info", "verbose" } +end + +local function GetOrderedDebugScopes() + local values = HMGT:GetDebugScopeOptions() or {} + local names = { "ALL" } + for scope in pairs(values) do + if scope ~= "ALL" then + names[#names + 1] = scope + end + end + table.sort(names, function(a, b) + if a == "ALL" then return true end + if b == "ALL" then return false end + return tostring(values[a] or a) < tostring(values[b] or b) + end) + return names, values +end + +local function SetFilterButtonText(buttonWidget, prefix, valueLabel) + if not buttonWidget then + return + end + buttonWidget:SetText(string.format("%s: %s", tostring(prefix or ""), tostring(valueLabel or ""))) +end + +local function AdvanceDebugLevel(step) + local levels = GetOrderedDebugLevels() + local current = HMGT:GetConfiguredDebugLevel() + local nextIndex = 1 + for index, value in ipairs(levels) do + if value == current then + nextIndex = index + (step or 1) + break + end + end + if nextIndex < 1 then + nextIndex = #levels + elseif nextIndex > #levels then + nextIndex = 1 + end + HMGT.db.profile.debugLevel = levels[nextIndex] + HMGT:RefreshDebugWindow() +end + +local function AdvanceDebugScope(step) + local scopes, labels = GetOrderedDebugScopes() + local current = (HMGT.db and HMGT.db.profile and HMGT.db.profile.debugScope) or "ALL" + local nextIndex = 1 + for index, value in ipairs(scopes) do + if value == current then + nextIndex = index + (step or 1) + break + end + end + if nextIndex < 1 then + nextIndex = #scopes + elseif nextIndex > #scopes then + nextIndex = 1 + end + HMGT.db.profile.debugScope = scopes[nextIndex] + HMGT:RefreshDebugWindow() +end + +function HMGT:SetDebugWindowMinimized(minimized) + local frame = self.debugWindow + if not frame then + return + end + + minimized = minimized and true or false + self.debugWindowStatus = self.debugWindowStatus or { + width = 860, + height = 340, + } + self.debugWindowStatus.minimized = minimized + + local collapsedHeight = 64 + if minimized then + self.debugWindowStatus.restoreHeight = self.debugWindowStatus.height or frame:GetHeight() or 340 + end + + local targetHeight = minimized + and collapsedHeight + or (self.debugWindowStatus.restoreHeight or self.debugWindowStatus.height or 340) + + if frame.aceWidget then + frame.aceWidget:EnableResize(not minimized) + frame.aceWidget:SetHeight(targetHeight) + else + frame:SetHeight(targetHeight) + end + + if frame.minimizeButton then + frame.minimizeButton:SetText(minimized and "+" or "-") + end + if frame.clearButton then + local buttonFrame = frame.clearButton.frame or frame.clearButton + buttonFrame:SetShown(not minimized) + end + if frame.selectButton then + local buttonFrame = frame.selectButton.frame or frame.selectButton + buttonFrame:SetShown(not minimized) + end + if frame.levelFilter then + local filterFrame = frame.levelFilter.frame or frame.levelFilter + filterFrame:SetShown(not minimized) + end + if frame.scopeFilter then + local filterFrame = frame.scopeFilter.frame or frame.scopeFilter + filterFrame:SetShown(not minimized) + end + if frame.logWidget then + frame.logWidget.frame:SetShown(not minimized) + end + if frame.scrollBG then + frame.scrollBG:SetShown(not minimized) + end + + if not minimized then + self:RefreshDebugWindow() + end +end + +function HMGT:ToggleDebugWindowMinimized() + self:SetDebugWindowMinimized(not (self.debugWindowStatus and self.debugWindowStatus.minimized)) +end + +function HMGT:EnsureDebugWindow() + if self.debugWindow then + return self.debugWindow + end + + local frameWidget + if AceGUI then + frameWidget = AceGUI:Create("Frame") + self.debugWindowStatus = self.debugWindowStatus or { + width = 860, + height = 340, + } + frameWidget:SetTitle(L["DEBUG_WINDOW_TITLE"] or "HMGT Debug Console") + frameWidget:SetStatusText(L["DEBUG_WINDOW_HINT"] or "Mouse wheel scrolls, Ctrl+A selects all, Ctrl+C copies selected text") + frameWidget:SetStatusTable(self.debugWindowStatus) + frameWidget:SetWidth(self.debugWindowStatus.width or 860) + frameWidget:SetHeight(self.debugWindowStatus.height or 340) + frameWidget:EnableResize(true) + frameWidget.frame:SetClampedToScreen(true) + frameWidget.frame:SetToplevel(true) + frameWidget.frame:SetFrameStrata("FULLSCREEN_DIALOG") + frameWidget:Hide() + end + + local frame = frameWidget and frameWidget.frame or CreateFrame("Frame", "HMGT_DebugWindow", UIParent, "BackdropTemplate") + if not frameWidget then + frame:SetSize(860, 340) + frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) + frame:SetFrameStrata("DIALOG") + frame:SetClampedToScreen(true) + frame:SetMovable(true) + frame:EnableMouse(true) + frame:RegisterForDrag("LeftButton") + frame:SetScript("OnDragStart", function(selfFrame) selfFrame:StartMoving() end) + frame:SetScript("OnDragStop", function(selfFrame) selfFrame:StopMovingOrSizing() end) + frame:SetBackdrop({ + bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + edgeSize = 12, + insets = { left = 3, right = 3, top = 3, bottom = 3 }, + }) + frame:SetBackdropColor(0.05, 0.05, 0.06, 0.95) + frame:SetBackdropBorderColor(0.35, 0.55, 0.85, 1) + frame:Hide() + + local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") + title:SetPoint("TOPLEFT", frame, "TOPLEFT", 14, -12) + title:SetText(L["DEBUG_WINDOW_TITLE"] or "HMGT Debug Console") + frame.title = title + + local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton") + closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -5, -5) + frame.closeButton = closeButton + + local minimizeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") + minimizeButton:SetSize(22, 20) + minimizeButton:SetPoint("TOPRIGHT", closeButton, "TOPLEFT", -2, 0) + minimizeButton:SetText((self.debugWindowStatus and self.debugWindowStatus.minimized) and "+" or "-") + minimizeButton:SetScript("OnClick", function() + HMGT:ToggleDebugWindowMinimized() + end) + frame.minimizeButton = minimizeButton + end + + frame.aceWidget = frameWidget + + if frameWidget and AceGUI then + local content = frameWidget.content + + local minimizeButton = AceGUI:Create("Button") + minimizeButton:SetText((self.debugWindowStatus and self.debugWindowStatus.minimized) and "+" or "-") + minimizeButton:SetWidth(24) + minimizeButton:SetCallback("OnClick", function() + HMGT:ToggleDebugWindowMinimized() + end) + minimizeButton.frame:SetParent(frame) + minimizeButton.frame:ClearAllPoints() + minimizeButton.frame:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -34, -4) + minimizeButton.frame:SetHeight(20) + minimizeButton.frame:Show() + frame.minimizeButton = minimizeButton + + local clearButton = AceGUI:Create("Button") + clearButton:SetText(L["OPT_DEBUG_CLEAR"] or "Clear log") + clearButton:SetWidth(120) + clearButton:SetCallback("OnClick", function() + HMGT:ClearDebugLog() + end) + clearButton.frame:SetParent(content) + clearButton.frame:ClearAllPoints() + clearButton.frame:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, -2) + clearButton.frame:Show() + frame.clearButton = clearButton + + local selectButton = AceGUI:Create("Button") + selectButton:SetText(L["OPT_DEBUG_SELECT_ALL"] or "Select all") + selectButton:SetWidth(120) + selectButton:SetCallback("OnClick", function() + if frame.editBox then + frame.editBox:SetFocus() + frame.editBox:HighlightText(0) + end + end) + selectButton.frame:SetParent(content) + selectButton.frame:ClearAllPoints() + selectButton.frame:SetPoint("TOPRIGHT", clearButton.frame, "TOPLEFT", -6, 0) + selectButton.frame:Show() + frame.selectButton = selectButton + + local levelFilter = AceGUI:Create("Button") + levelFilter:SetWidth(150) + levelFilter:SetCallback("OnClick", function() + AdvanceDebugLevel(1) + end) + levelFilter.frame:SetParent(content) + levelFilter.frame:ClearAllPoints() + levelFilter.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, 0) + levelFilter.frame:Show() + frame.levelFilter = levelFilter + + local scopeFilter = AceGUI:Create("Button") + scopeFilter:SetWidth(180) + scopeFilter:SetCallback("OnClick", function() + AdvanceDebugScope(1) + end) + scopeFilter.frame:SetParent(content) + scopeFilter.frame:ClearAllPoints() + scopeFilter.frame:SetPoint("TOPLEFT", levelFilter.frame, "TOPRIGHT", 8, 0) + scopeFilter.frame:Show() + frame.scopeFilter = scopeFilter + + local logWidget = AceGUI:Create("MultiLineEditBox") + logWidget:SetLabel("") + logWidget:DisableButton(true) + logWidget:SetNumLines(18) + logWidget:SetText("") + logWidget.frame:SetParent(content) + logWidget.frame:ClearAllPoints() + logWidget.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -54) + logWidget.frame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", 0, 0) + logWidget.frame:Show() + logWidget:SetCallback("OnTextChanged", function() + HMGT:RefreshDebugWindow() + end) + logWidget.editBox:SetScript("OnKeyDown", function(selfBox, key) + if IsControlKeyDown() and (key == "A" or key == "a") then + selfBox:HighlightText(0) + end + end) + frame.logWidget = logWidget + frame.editBox = logWidget.editBox + frame.scrollFrame = logWidget.scrollFrame + + self.debugWindow = frame + self:SetDebugWindowMinimized(self.debugWindowStatus and self.debugWindowStatus.minimized) + return frame + end + + local clearButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") + clearButton:SetSize(90, 22) + clearButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -30, -6) + clearButton:SetText(L["OPT_DEBUG_CLEAR"] or "Clear log") + clearButton:SetScript("OnClick", function() + HMGT:ClearDebugLog() + end) + frame.clearButton = clearButton + + local selectButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") + selectButton:SetSize(90, 22) + selectButton:SetPoint("TOPRIGHT", clearButton, "TOPLEFT", -6, 0) + selectButton:SetText(L["OPT_DEBUG_SELECT_ALL"] or "Select all") + selectButton:SetScript("OnClick", function() + if frame.editBox then + frame.editBox:SetFocus() + frame.editBox:HighlightText(0) + end + end) + frame.selectButton = selectButton + + local scopeFilter = CreateFrame("Frame", nil, frame) + scopeFilter:SetSize(170, 22) + scopeFilter:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -8) + frame.scopeFilter = scopeFilter + + local scrollBG = CreateFrame("Frame", nil, frame, "BackdropTemplate") + scrollBG:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + edgeSize = 16, + insets = { left = 4, right = 3, top = 4, bottom = 3 }, + }) + scrollBG:SetBackdropColor(0, 0, 0, 0.95) + scrollBG:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) + scrollBG:SetPoint("TOPLEFT", frame, "TOPLEFT", 14, -36) + scrollBG:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -30, 14) + frame.scrollBG = scrollBG + + local scrollFrame = CreateFrame("ScrollFrame", nil, scrollBG, "UIPanelScrollFrameTemplate") + scrollFrame:SetPoint("TOPLEFT", scrollBG, "TOPLEFT", 6, -6) + scrollFrame:SetPoint("BOTTOMRIGHT", scrollBG, "BOTTOMRIGHT", -27, 4) + scrollFrame:EnableMouseWheel(true) + scrollFrame:SetScript("OnMouseWheel", function(selfMsg, delta) + if delta > 0 then + selfMsg:SetVerticalScroll(math.max(0, selfMsg:GetVerticalScroll() - 42)) + else + selfMsg:SetVerticalScroll(selfMsg:GetVerticalScroll() + 42) + end + end) + frame.scrollFrame = scrollFrame + + local editBox = CreateFrame("EditBox", nil, scrollFrame) + editBox:SetMultiLine(true) + editBox:SetAutoFocus(false) + editBox:SetFontObject(ChatFontNormal) + editBox:SetWidth(780) + editBox:SetTextInsets(6, 6, 6, 6) + editBox:EnableMouse(true) + editBox:SetScript("OnEscapePressed", function(selfBox) + selfBox:ClearFocus() + end) + editBox:SetScript("OnKeyDown", function(selfBox, key) + if IsControlKeyDown() and (key == "A" or key == "a") then + selfBox:HighlightText(0) + end + end) + editBox:SetScript("OnTextChanged", function(selfBox, userInput) + if userInput then + HMGT:RefreshDebugWindow() + else + selfBox:SetCursorPosition(selfBox:GetNumLetters()) + selfBox:SetHeight(math.max(scrollFrame:GetHeight(), HMGT:GetDebugWindowTextHeight(frame, selfBox:GetText()) + 16)) + scrollFrame:UpdateScrollChildRect() + end + end) + editBox:SetScript("OnMouseUp", function(selfBox) + selfBox:SetFocus() + end) + scrollFrame:SetScrollChild(editBox) + frame.editBox = editBox + + local measureText = frame:CreateFontString(nil, "ARTWORK", "ChatFontNormal") + measureText:SetJustifyH("LEFT") + measureText:SetJustifyV("TOP") + if measureText.SetSpacing then + measureText:SetSpacing(2) + end + measureText:SetWidth(768) + frame.measureText = measureText + + self.debugWindow = frame + self:SetDebugWindowMinimized(self.debugWindowStatus and self.debugWindowStatus.minimized) + return frame +end + +function HMGT:GetDebugWindowTextHeight(frame, text) + if not frame or not frame.measureText then + return 0 + end + + local width = 768 + if frame.editBox then + width = math.max(1, (frame.editBox:GetWidth() or width) - 12) + end + frame.measureText:SetWidth(width) + frame.measureText:SetText(text or "") + return frame.measureText:GetStringHeight() +end + +function HMGT:RefreshDebugWindow() + local frame = self:EnsureDebugWindow() + if not frame then + return + end + + local filtered = self:GetFilteredDebugBuffer() or self.debugBuffer or {} + local text = table.concat(filtered, "\n") + if frame.logWidget and frame.editBox then + if frame.levelFilter then + local levelOptions = self:GetDebugLevelOptions() + SetFilterButtonText(frame.levelFilter, L["OPT_DEBUG_LEVEL"] or "Level", levelOptions[self:GetConfiguredDebugLevel()]) + end + if frame.scopeFilter then + local scopeOptions = self:GetDebugScopeOptions() + local currentScope = (self.db and self.db.profile and self.db.profile.debugScope) or "ALL" + SetFilterButtonText(frame.scopeFilter, L["OPT_DEBUG_SCOPE"] or "Module", scopeOptions[currentScope] or currentScope) + end + frame.logWidget:SetText(text) + frame.editBox:SetCursorPosition(frame.editBox:GetNumLetters()) + return + end + + if not frame.editBox then + return + end + + frame.editBox:SetText(text) + frame.editBox:SetCursorPosition(#text) + frame.editBox:SetHeight(math.max(frame.scrollFrame:GetHeight(), self:GetDebugWindowTextHeight(frame, text) + 16)) + frame.scrollFrame:SetVerticalScroll(math.max(0, frame.editBox:GetHeight() - frame.scrollFrame:GetHeight())) +end + +function HMGT:UpdateDebugWindowVisibility() + if self.db and self.db.profile then + self.db.profile.debug = false + end + local frame = self.debugWindow + if not frame then + return + end + local widget = frame.aceWidget + if widget then + widget:Hide() + else + frame:Hide() + end +end + +function HMGT:ClearDebugLog() + wipe(self.debugBuffer) + if self.debugWindow and self.debugWindow.logWidget then + self.debugWindow.logWidget:SetText("") + self.debugWindow.editBox:SetCursorPosition(0) + self.debugWindow.scrollFrame:SetVerticalScroll(0) + return + end + if self.debugWindow and self.debugWindow.editBox then + self.debugWindow.editBox:SetText("") + self.debugWindow.scrollFrame:SetVerticalScroll(0) + end +end + +function HMGT:ToggleDebugWindowShortcut() + if self.db and self.db.profile then + self.db.profile.debug = false + end + local frame = self.debugWindow + if not frame then + return + end + local widget = frame.aceWidget + if widget then + widget:Hide() + else + frame:Hide() + end +end + +function HMGT:DumpDebugLog(maxLines) + return +end diff --git a/Core/DevTools.lua b/Core/DevTools.lua new file mode 100644 index 0000000..5df2382 --- /dev/null +++ b/Core/DevTools.lua @@ -0,0 +1,227 @@ +local ADDON_NAME = "HailMaryGuildTools" +local HMGT = _G[ADDON_NAME] +if not HMGT then return end + +local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME) + +HMGT.devToolsBuffer = HMGT.devToolsBuffer or {} +HMGT.devToolsBufferMax = HMGT.devToolsBufferMax or 300 + +local DEVTOOLS_SCOPE_ALL = "ALL" +local DEVTOOLS_SCOPE_LABELS = { + System = "System", + Version = "Version", + Options = "Options", + Comm = "Communication", + Tracker = "Tracker", + RaidTimeline = "Raid Timeline", + Notes = "Notes", +} + +local DEVTOOLS_LEVELS = { + error = 1, + trace = 2, +} + +local function TrimText(value) + return tostring(value or ""):gsub("^%s+", ""):gsub("%s+$", "") +end + +local function SortKeys(tbl) + local keys = {} + for key in pairs(tbl or {}) do + keys[#keys + 1] = key + end + table.sort(keys, function(a, b) + return tostring(a) < tostring(b) + end) + return keys +end + +local function EncodePayloadValue(value, depth) + depth = tonumber(depth) or 0 + local valueType = type(value) + if valueType == "nil" then + return "nil" + end + if valueType == "string" then + return value + end + if valueType == "number" or valueType == "boolean" then + return tostring(value) + end + if valueType == "table" then + if depth >= 1 then + return "{...}" + end + local parts = {} + for _, key in ipairs(SortKeys(value)) do + parts[#parts + 1] = string.format("%s=%s", tostring(key), EncodePayloadValue(value[key], depth + 1)) + end + return table.concat(parts, ", ") + end + return tostring(value) +end + +function HMGT:GetDevToolsSettings() + local profile = self.db and self.db.profile + if not profile then + return { + enabled = false, + level = "error", + scope = DEVTOOLS_SCOPE_ALL, + window = { width = 920, height = 420, minimized = false }, + } + end + profile.devTools = type(profile.devTools) == "table" and profile.devTools or {} + local settings = profile.devTools + settings.enabled = settings.enabled == true + if settings.level ~= "error" and settings.level ~= "trace" then + settings.level = "error" + end + if type(settings.scope) ~= "string" or settings.scope == "" then + settings.scope = DEVTOOLS_SCOPE_ALL + end + settings.window = type(settings.window) == "table" and settings.window or {} + settings.window.width = math.max(720, tonumber(settings.window.width) or 920) + settings.window.height = math.max(260, tonumber(settings.window.height) or 420) + settings.window.minimized = settings.window.minimized == true + return settings +end + +function HMGT:IsDevToolsEnabled() + return self:GetDevToolsSettings().enabled == true +end + +function HMGT:GetDevToolsLevelOptions() + return { + error = L["OPT_DEVTOOLS_LEVEL_ERROR"] or "Errors", + trace = L["OPT_DEVTOOLS_LEVEL_TRACE"] or "Trace", + } +end + +function HMGT:GetConfiguredDevToolsLevel() + return self:GetDevToolsSettings().level or "error" +end + +function HMGT:ShouldIncludeDevToolsLevel(level) + local configured = self:GetConfiguredDevToolsLevel() + return (DEVTOOLS_LEVELS[tostring(level or "error")] or DEVTOOLS_LEVELS.error) + <= (DEVTOOLS_LEVELS[configured] or DEVTOOLS_LEVELS.error) +end + +function HMGT:GetDevToolsScopeOptions() + local values = { + [DEVTOOLS_SCOPE_ALL] = L["OPT_DEVTOOLS_SCOPE_ALL"] or "All scopes", + } + for scope, label in pairs(DEVTOOLS_SCOPE_LABELS) do + values[scope] = label + end + for _, entry in ipairs(self.devToolsBuffer or {}) do + local scope = TrimText(entry and entry.scope or "") + if scope ~= "" and scope ~= DEVTOOLS_SCOPE_ALL then + values[scope] = values[scope] or scope + end + end + return values +end + +function HMGT:FormatDevToolsEntry(entry) + local stamp = tostring(entry and entry.stamp or date("%H:%M:%S")) + local level = string.upper(tostring(entry and entry.level or "error")) + local scope = tostring(entry and entry.scope or "System") + local eventName = tostring(entry and entry.event or "") + local payload = TrimText(entry and entry.payload or "") + if payload ~= "" then + return string.format("%s [%s][%s] %s | %s", stamp, level, scope, eventName, payload) + end + return string.format("%s [%s][%s] %s", stamp, level, scope, eventName) +end + +function HMGT:GetFilteredDevToolsEntries() + local filtered = {} + local settings = self:GetDevToolsSettings() + for _, entry in ipairs(self.devToolsBuffer or {}) do + local scopeMatches = settings.scope == DEVTOOLS_SCOPE_ALL or settings.scope == tostring(entry.scope or "") + if scopeMatches and self:ShouldIncludeDevToolsLevel(entry.level) then + filtered[#filtered + 1] = entry + end + end + return filtered +end + +function HMGT:GetFilteredDevToolsLines() + local lines = {} + for _, entry in ipairs(self:GetFilteredDevToolsEntries()) do + lines[#lines + 1] = self:FormatDevToolsEntry(entry) + end + return lines +end + +function HMGT:RecordDevEvent(level, scope, eventName, payload) + if not self:IsDevToolsEnabled() then + return + end + + local normalizedLevel = tostring(level or "error") + if normalizedLevel ~= "error" and normalizedLevel ~= "trace" then + normalizedLevel = "trace" + end + if not self:ShouldIncludeDevToolsLevel(normalizedLevel) then + return + end + + local normalizedScope = TrimText(scope or "System") + if normalizedScope == "" then + normalizedScope = "System" + end + + local entry = { + stamp = date("%H:%M:%S"), + level = normalizedLevel, + scope = normalizedScope, + event = TrimText(eventName or "event"), + payload = EncodePayloadValue(payload, 0), + } + + table.insert(self.devToolsBuffer, entry) + if #self.devToolsBuffer > (tonumber(self.devToolsBufferMax) or 300) then + table.remove(self.devToolsBuffer, 1) + end + + if self.devToolsWindow and self.devToolsWindow:IsShown() and self.RefreshDevToolsWindow then + self:RefreshDevToolsWindow() + end +end + +function HMGT:DevError(scope, eventName, payload) + self:RecordDevEvent("error", scope, eventName, payload) +end + +function HMGT:DevTrace(scope, eventName, payload) + self:RecordDevEvent("trace", scope, eventName, payload) +end + +function HMGT:ClearDevToolsLog() + wipe(self.devToolsBuffer) + if self.devToolsWindow and self.devToolsWindow.editBox then + self.devToolsWindow.editBox:SetText("") + self.devToolsWindow.editBox:SetCursorPosition(0) + end +end + +function HMGT:DumpDevToolsLog(maxLines) + if self:IsDevToolsEnabled() and self.OpenDevToolsWindow then + self:OpenDevToolsWindow() + return + end + + local lines = tonumber(maxLines) or 40 + if lines < 1 then + lines = 1 + end + local startIndex = math.max(1, #self.devToolsBuffer - lines + 1) + for i = startIndex, #self.devToolsBuffer do + self:Print(self:FormatDevToolsEntry(self.devToolsBuffer[i])) + end +end diff --git a/Core/DevToolsWindow.lua b/Core/DevToolsWindow.lua new file mode 100644 index 0000000..610a35e --- /dev/null +++ b/Core/DevToolsWindow.lua @@ -0,0 +1,254 @@ +local ADDON_NAME = "HailMaryGuildTools" +local HMGT = _G[ADDON_NAME] +if not HMGT then return end + +local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME) +local AceGUI = LibStub("AceGUI-3.0", true) +if not AceGUI then return end + +local function GetOrderedLevels() + return { "error", "trace" } +end + +local function GetOrderedScopes() + local values = HMGT:GetDevToolsScopeOptions() or {} + local scopes = { "ALL" } + for scope in pairs(values) do + if scope ~= "ALL" then + scopes[#scopes + 1] = scope + end + end + table.sort(scopes, function(a, b) + if a == "ALL" then return true end + if b == "ALL" then return false end + return tostring(values[a] or a) < tostring(values[b] or b) + end) + return scopes, values +end + +local function SetFilterButtonText(buttonWidget, prefix, valueLabel) + if not buttonWidget then + return + end + buttonWidget:SetText(string.format("%s: %s", tostring(prefix or ""), tostring(valueLabel or ""))) +end + +local function AdvanceLevel(step) + local levels = GetOrderedLevels() + local current = HMGT:GetConfiguredDevToolsLevel() + local nextIndex = 1 + for index, value in ipairs(levels) do + if value == current then + nextIndex = index + (step or 1) + break + end + end + if nextIndex < 1 then + nextIndex = #levels + elseif nextIndex > #levels then + nextIndex = 1 + end + HMGT:GetDevToolsSettings().level = levels[nextIndex] + HMGT:RefreshDevToolsWindow() +end + +local function AdvanceScope(step) + local scopes = GetOrderedScopes() + local current = HMGT:GetDevToolsSettings().scope or "ALL" + local nextIndex = 1 + for index, value in ipairs(scopes) do + if value == current then + nextIndex = index + (step or 1) + break + end + end + if nextIndex < 1 then + nextIndex = #scopes + elseif nextIndex > #scopes then + nextIndex = 1 + end + HMGT:GetDevToolsSettings().scope = scopes[nextIndex] + HMGT:RefreshDevToolsWindow() +end + +function HMGT:EnsureDevToolsWindow() + if self.devToolsWindow then + return self.devToolsWindow + end + + local settings = self:GetDevToolsSettings() + local window = self:CreateAceWindow("devTools", { + title = L["DEVTOOLS_WINDOW_TITLE"] or "HMGT Developer Tools", + statusText = L["DEVTOOLS_WINDOW_HINT"] or "Structured developer events for the current session", + statusTable = settings.window, + width = settings.window.width or 920, + height = settings.window.height or 420, + minimizable = true, + minimizedHeight = 64, + }) + if not window then + return nil + end + + local content = window:GetContent() + + local clearButton = AceGUI:Create("Button") + clearButton:SetText(L["OPT_DEVTOOLS_CLEAR"] or "Clear developer log") + clearButton:SetWidth(140) + clearButton:SetCallback("OnClick", function() + HMGT:ClearDevToolsLog() + end) + clearButton.frame:SetParent(content) + clearButton.frame:ClearAllPoints() + clearButton.frame:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, -2) + clearButton.frame:Show() + window.clearButton = clearButton + window:RegisterMinimizeTarget(clearButton) + + local selectButton = AceGUI:Create("Button") + selectButton:SetText(L["OPT_DEVTOOLS_SELECT_ALL"] or "Select all") + selectButton:SetWidth(120) + selectButton:SetCallback("OnClick", function() + if window.editBox then + window.editBox:SetFocus() + window.editBox:HighlightText(0) + end + end) + selectButton.frame:SetParent(content) + selectButton.frame:ClearAllPoints() + selectButton.frame:SetPoint("TOPRIGHT", clearButton.frame, "TOPLEFT", -6, 0) + selectButton.frame:Show() + window.selectButton = selectButton + window:RegisterMinimizeTarget(selectButton) + + local levelFilter = AceGUI:Create("Button") + levelFilter:SetWidth(150) + levelFilter:SetCallback("OnClick", function() + AdvanceLevel(1) + end) + levelFilter.frame:SetParent(content) + levelFilter.frame:ClearAllPoints() + levelFilter.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, 0) + levelFilter.frame:Show() + window.levelFilter = levelFilter + window:RegisterMinimizeTarget(levelFilter) + + local scopeFilter = AceGUI:Create("Button") + scopeFilter:SetWidth(200) + scopeFilter:SetCallback("OnClick", function() + AdvanceScope(1) + end) + scopeFilter.frame:SetParent(content) + scopeFilter.frame:ClearAllPoints() + scopeFilter.frame:SetPoint("TOPLEFT", levelFilter.frame, "TOPRIGHT", 8, 0) + scopeFilter.frame:Show() + window.scopeFilter = scopeFilter + window:RegisterMinimizeTarget(scopeFilter) + + local logWidget = AceGUI:Create("MultiLineEditBox") + logWidget:SetLabel("") + logWidget:DisableButton(true) + logWidget:SetNumLines(20) + logWidget:SetText("") + logWidget.frame:SetParent(content) + logWidget.frame:ClearAllPoints() + logWidget.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -54) + logWidget.frame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", 0, 0) + logWidget.frame:Show() + logWidget.editBox:SetScript("OnKeyDown", function(selfBox, key) + if IsControlKeyDown() and (key == "A" or key == "a") then + selfBox:HighlightText(0) + end + end) + window.logWidget = logWidget + window.editBox = logWidget.editBox + window:RegisterMinimizeTarget(logWidget) + + self.devToolsWindow = window + window:SetMinimized(settings.window.minimized) + return window +end + +function HMGT:RefreshDevToolsWindow() + local window = self:EnsureDevToolsWindow() + if not window or not window.editBox or not window.logWidget then + return + end + + local levelOptions = self:GetDevToolsLevelOptions() + SetFilterButtonText(window.levelFilter, L["OPT_DEVTOOLS_LEVEL"] or "Capture level", levelOptions[self:GetConfiguredDevToolsLevel()]) + + local scopeValues = self:GetDevToolsScopeOptions() + local currentScope = self:GetDevToolsSettings().scope or "ALL" + SetFilterButtonText(window.scopeFilter, L["OPT_DEVTOOLS_SCOPE"] or "Scope", scopeValues[currentScope] or currentScope) + + local text = table.concat(self:GetFilteredDevToolsLines(), "\n") + window.logWidget:SetText(text) + window.editBox:SetCursorPosition(window.editBox:GetNumLetters()) +end + +function HMGT:OpenDevToolsWindow() + if not self:IsDevToolsEnabled() then + self:Print(L["OPT_DEVTOOLS_DISABLED"] or "HMGT: developer tools are not enabled.") + return + end + local window = self:EnsureDevToolsWindow() + if not window then + return + end + window:Show() + window:Raise() + self:RefreshDevToolsWindow() +end + +function HMGT:ToggleDevToolsWindow() + if not self:IsDevToolsEnabled() then + self:Print(L["OPT_DEVTOOLS_DISABLED"] or "HMGT: developer tools are not enabled.") + return + end + local window = self:EnsureDevToolsWindow() + if not window then + return + end + if window:IsShown() then + window:Hide() + return + end + window:Show() + window:Raise() + self:RefreshDevToolsWindow() +end + +function HMGT:UpdateDevToolsWindowVisibility() + local window = self.devToolsWindow + if not window then + return + end + if not self:IsDevToolsEnabled() then + window:Hide() + return + end + if window:IsShown() then + self:RefreshDevToolsWindow() + end +end + +function HMGT:RefreshDebugWindow() + self:RefreshDevToolsWindow() +end + +function HMGT:UpdateDebugWindowVisibility() + self:UpdateDevToolsWindowVisibility() +end + +function HMGT:ClearDebugLog() + self:ClearDevToolsLog() +end + +function HMGT:ToggleDebugWindowShortcut() + self:ToggleDevToolsWindow() +end + +function HMGT:DumpDebugLog(maxLines) + self:DumpDevToolsLog(maxLines) +end diff --git a/Core/VersionNoticeWindow.lua b/Core/VersionNoticeWindow.lua new file mode 100644 index 0000000..c6bbf2b --- /dev/null +++ b/Core/VersionNoticeWindow.lua @@ -0,0 +1,103 @@ +local ADDON_NAME = "HailMaryGuildTools" +local HMGT = _G[ADDON_NAME] +if not HMGT then return end + +local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME) + +function HMGT:EnsureVersionNoticeWindow() + if self.versionNoticeWindow then + return self.versionNoticeWindow + end + + self.versionNoticeWindowStatus = self.versionNoticeWindowStatus or { + width = 560, + height = 240, + } + + local window = self:CreateAceWindow("versionNotice", { + title = L["VERSION_WINDOW_TITLE"] or "HMGT Version Check", + statusText = "", + statusTable = self.versionNoticeWindowStatus, + width = self.versionNoticeWindowStatus.width or 560, + height = self.versionNoticeWindowStatus.height or 240, + backgroundTexture = "Interface\\AddOns\\HailMaryGuildTools\\Media\\HailMaryLogo.png", + backgroundWidth = 220, + backgroundHeight = 120, + backgroundOffsetY = -8, + backgroundAlpha = 0.12, + strata = "FULLSCREEN_DIALOG", + }) + if not window then + return nil + end + + local content = window:GetContent() + local messageText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") + messageText:SetPoint("TOPLEFT", content, "TOPLEFT", 28, -28) + messageText:SetPoint("TOPRIGHT", content, "TOPRIGHT", -28, -28) + messageText:SetJustifyH("CENTER") + messageText:SetJustifyV("MIDDLE") + messageText:SetTextColor(1, 0.82, 0.1, 1) + messageText:SetText(L["VERSION_WINDOW_MESSAGE"] or "A new version of Hail Mary Guild Tools is available.") + window.messageText = messageText + + local detailText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + detailText:SetPoint("TOPLEFT", messageText, "BOTTOMLEFT", 0, -18) + detailText:SetPoint("TOPRIGHT", messageText, "BOTTOMRIGHT", 0, -18) + detailText:SetJustifyH("CENTER") + detailText:SetJustifyV("TOP") + if detailText.SetSpacing then + detailText:SetSpacing(2) + end + detailText:SetTextColor(0.9, 0.9, 0.9, 1) + window.detailText = detailText + + self.versionNoticeWindow = window + return window +end + +function HMGT:ShowVersionMismatchPopup(playerName, detail, sourceTag, opts) + opts = opts or {} + + if playerName or detail or sourceTag then + self.latestVersionMismatch = { + playerName = playerName, + detail = detail, + sourceTag = sourceTag, + } + end + + local info = self.latestVersionMismatch or {} + local window = self:EnsureVersionNoticeWindow() + if not window then + return + end + local hasMismatch = info.playerName or info.detail + + window:SetTitle(L["VERSION_WINDOW_TITLE"] or "HMGT Version Check") + + if hasMismatch then + window.messageText:SetText(L["VERSION_WINDOW_MESSAGE"] or "A new version of Hail Mary Guild Tools is available.") + window.detailText:SetText(string.format( + L["VERSION_WINDOW_DETAIL"] or "Detected via %s from %s.\n%s", + tostring(info.sourceTag or "?"), + tostring(info.playerName or UNKNOWN), + tostring(info.detail or "") + )) + else + window.messageText:SetText(L["VERSION_WINDOW_NO_MISMATCH"] or "No newer HMGT version has been detected in your current group.") + window.detailText:SetText(string.format( + L["VERSION_WINDOW_CURRENT"] or "Current version: %s | Protocol: %s", + tostring(HMGT.ADDON_VERSION or "dev"), + tostring(HMGT.PROTOCOL_VERSION or "?") + )) + end + + self:DevTrace("Version", hasMismatch and "window_show_mismatch" or "window_show_current", { + player = info.playerName, + source = info.sourceTag, + detail = info.detail, + }) + window:Show() + window:Raise() +end diff --git a/Embeds.xml b/Embeds.xml new file mode 100644 index 0000000..cd5f7da --- /dev/null +++ b/Embeds.xml @@ -0,0 +1,44 @@ + + + +