From f8b43e0fbba8c97e22751aea5cf212b7f898bfd8 Mon Sep 17 00:00:00 2001 From: Kevin Krol Date: Thu, 13 Jun 2024 02:13:40 +0200 Subject: [PATCH] Add initial support for The War Within --- .luacheckrc | 8 + .vscode/settings.json | 5 +- Clicked/Clicked.toc | 2 +- Clicked/Config/Bindings.lua | 50 ++- Clicked/Core/AttributeHandler.lua | 2 +- Clicked/Core/BindingProcessor.lua | 19 +- Clicked/Core/CommandProcessor.lua | 28 ++ Clicked/Core/Database.lua | 54 ++- Clicked/Core/Init.lua | 3 +- Clicked/Core/LocaleUtils.lua | 15 +- Clicked/Core/Upgrader.lua | 11 +- Clicked/Core/Utils.lua | 85 +++-- Clicked/Definitions.lua | 2 + Clicked/Libs/AceComm-3.0/AceComm-3.0.lua | 10 +- Clicked/Libs/AceComm-3.0/ChatThrottleLib.lua | 356 +++++++++++++----- Clicked/Libs/AceConfig-3.0/AceConfig-3.0.lua | 4 +- Clicked/Libs/AceDB-3.0/AceDB-3.0.lua | 8 +- .../LibMacroSyntaxHighlight.lua | 18 +- .../AceGUIContainer-ClickedTreeGroup.lua | 21 ++ ClickedMedia/ClickedMedia.toc | 2 +- 20 files changed, 520 insertions(+), 183 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 06cb7b27..6727ae93 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -45,6 +45,7 @@ read_globals = { "BOOKTYPE_PROFESSION", "BOOKTYPE_SPELL", "CreateFrame", + "CreateMacro", "DisableAddOn", "Constants", "C_AddOns", @@ -56,11 +57,14 @@ read_globals = { "C_PetJournal", "C_PvP", "C_SpecializationInfo", + "C_Spell", "C_Traits", "C_Timer", "ClassTalentFrame", "ClearCursor", + "DeleteMacro", "EasyMenu", + "EditMacro", "EnableAddOn", "Enum", "FillLocalizedClassList", @@ -90,6 +94,7 @@ read_globals = { "GetMouseFocus", "GetNumBindings", "GetNumGroupMembers", + "GetNumMacros", "GetNumShapeshiftForms", "GetNumSpecializations", "GetNumSpellTabs", @@ -179,7 +184,10 @@ read_globals = { "TalentButtonUtil", "TalentUtil", "tContains", + "ToggleDropDownMenu", "TooltipDataProcessor", + "UIDropDownMenu_AddButton", + "UIDropDownMenu_Initialize", "UIParent", "UnitClass", "UnitGUID", diff --git a/.vscode/settings.json b/.vscode/settings.json index f3c518fc..16f19605 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -175,7 +175,10 @@ "SpellButton_OnEnter", "SpellButton_OnLeave", "WeakAuras", - "WeakAurasSaved" + "WeakAurasSaved", + "UIDropDownMenu_Initialize", + "ToggleDropDownMenu", + "UIDropDownMenu_AddButton" ] } diff --git a/Clicked/Clicked.toc b/Clicked/Clicked.toc index fcc9b518..bc3fb875 100644 --- a/Clicked/Clicked.toc +++ b/Clicked/Clicked.toc @@ -1,4 +1,4 @@ -## Interface: 100207 +## Interface: 110000 ## Title: Clicked ## IconTexture: Interface\Icons\inv_misc_punchcards_yellow ## Author: Snakybo diff --git a/Clicked/Config/Bindings.lua b/Clicked/Config/Bindings.lua index a2109ed4..733ddf15 100644 --- a/Clicked/Config/Bindings.lua +++ b/Clicked/Config/Bindings.lua @@ -294,10 +294,10 @@ end --- @param input string|number --- @param mode string ---- @return string|integer name +--- @return string|integer? name --- @return integer? id local function GetSpellItemNameAndId(input, mode) - --- @type string|integer + --- @type string|integer? local name --- @type integer? @@ -305,8 +305,10 @@ local function GetSpellItemNameAndId(input, mode) if mode == Addon.BindingTypes.SPELL then if type(input) == "number" then + local spell = Addon:GetSpellInfo(input) + id = input - name = Addon:GetSpellInfo(id) + name = spell ~= nil and spell.name or nil else name = input id = Addon:GetSpellId(name) @@ -510,8 +512,11 @@ local function HijackSpellFlyout_Toggle() end) button:SetScript("OnClick", function() - local name = Addon:GetSpellInfo(parent.spellID); - OnSpellBookButtonClick(name) + local spell = Addon:GetSpellInfo(parent.spellID); + + if spell ~= nil then + OnSpellBookButtonClick(spell.name) + end end) spellFlyOutButtons[id] = button @@ -1326,7 +1331,8 @@ local function DrawSpellItemAuraSelection(container, action, mode) action[valueKey] = linkId if mode == Addon.BindingTypes.SPELL then - value = Addon:GetSpellInfo(linkId) + local spell = Addon:GetSpellInfo(linkId) + value = spell ~= nil and spell.name or nil elseif mode == Addon.BindingTypes.ITEM then value = Addon:GetItemInfo(linkId) end @@ -1390,7 +1396,8 @@ local function DrawSpellItemAuraSelection(container, action, mode) local icon if mode == Addon.BindingTypes.SPELL then - icon = select(3, Addon:GetSpellInfo(id)) + local spell = Addon:GetSpellInfo(id) + icon = spell and spell.iconID or nil elseif mode == Addon.BindingTypes.ITEM then icon = select(10, Addon:GetItemInfo(id)) end @@ -1430,6 +1437,10 @@ local function DrawSpellItemAuraSelection(container, action, mode) widget:SetText(Addon.L["Pick from spellbook"]) widget:SetCallback("OnClick", OnClick) + if Addon.EXPANSION_LEVEL >= Addon.EXPANSION.TWW then + widget:SetDisabled(true) + end + if hasRank then widget:SetRelativeWidth(0.65) else @@ -1454,7 +1465,8 @@ local function DrawSpellItemAuraSelection(container, action, mode) return end - action[valueKey] = Addon:GetSpellInfo(id, false) + local spell = Addon:GetSpellInfo(id, false) + action[valueKey] = spell ~= nil and spell.name or nil action.convertValueToId = false Clicked:ReloadBinding(binding, true) @@ -3432,11 +3444,17 @@ end -- Private addon API function Addon:BindingConfig_Initialize() - SpellBookFrame:HookScript("OnHide", function() - HijackSpellButton_UpdateButton(nil) - end) + if Addon.EXPANSION_LEVEL >= Addon.EXPANSION.TWW then + -- TODO: Spellbook + else + SpellBookFrame:HookScript("OnHide", function() + HijackSpellButton_UpdateButton(nil) + end) + end - if Addon.EXPANSION_LEVEL >= Addon.EXPANSION.CATA then + if Addon.EXPANSION_LEVEL >= Addon.EXPANSION.TWW then + -- TODO: Spellbook + elseif Addon.EXPANSION_LEVEL >= Addon.EXPANSION.CATA then for i = 1, SPELLS_PER_PAGE do local currSpellButton = _G["SpellButton" .. i]; hooksecurefunc(currSpellButton, "UpdateButton", HijackSpellButton_UpdateButton) @@ -3561,8 +3579,12 @@ function Addon:BindingConfig_Redraw() return end - if not InCombatLockdown() and SpellBookFrame:IsShown() and SpellBookFrame.bookType == BOOKTYPE_SPELL then - SpellBookFrame_UpdateSpells() + if Addon.EXPANSION_LEVEL >= Addon.EXPANSION.TWW then + -- TODO: Spellbook + else + if not InCombatLockdown() and SpellBookFrame:IsShown() and SpellBookFrame.bookType == BOOKTYPE_SPELL then + SpellBookFrame_UpdateSpells() + end end root:SetStatusText(string.format("%s | %s", Clicked.VERSION, Addon.db:GetCurrentProfile())) diff --git a/Clicked/Core/AttributeHandler.lua b/Clicked/Core/AttributeHandler.lua index 0867515f..f2ad3457 100644 --- a/Clicked/Core/AttributeHandler.lua +++ b/Clicked/Core/AttributeHandler.lua @@ -150,7 +150,7 @@ function Addon:CreateCommandAttributes(register, command, prefix, suffix) end CreateAttribute(register, prefix, attributeType, suffix, "macro") - CreateAttribute(register, prefix, "macrotext", suffix, command.data) + CreateAttribute(register, prefix, "macro", suffix, command.macroName) else error("Unhandled action type: " .. command.action) end diff --git a/Clicked/Core/BindingProcessor.lua b/Clicked/Core/BindingProcessor.lua index 9ada656b..e1ed6eea 100644 --- a/Clicked/Core/BindingProcessor.lua +++ b/Clicked/Core/BindingProcessor.lua @@ -414,6 +414,7 @@ local function ProcessBuckets() if Addon:GetInternalBindingType(reference) == Addon.BindingTypes.MACRO then command.action = Addon.CommandType.MACRO command.data = Addon:GetMacroForBindings(bindings, interactionType) + command.macroName = Addon:GetMacroName(bindings, interactionType) elseif reference.type == Addon.BindingTypes.UNIT_SELECT then command.action = Addon.CommandType.TARGET @@ -501,6 +502,9 @@ local function GenerateBuckets(bindings) end end end + + table.sort(hovercastBucket, function(a, b) return a.uid < b.uid end) + table.sort(regularBucket, function(a, b) return a.uid < b.uid end) end --- @param str string @@ -1239,8 +1243,8 @@ function Addon:UpdateBindingLoadState(binding, options) -- implies, using the GetSpellInfo function on an item also returns a valid value. local function IsSpellKnown(value) - local name, _, _, _, _, _, spellId = Addon:GetSpellInfo(value) - return name ~= nil and spellId ~= nil and IsSpellKnownOrOverridesKnown(spellId) + local spell = Addon:GetSpellInfo(value) + return spell ~= nil and IsSpellKnownOrOverridesKnown(spell.spellID) or false end state.spellKnown = ValidateLoadOption(load.spellKnown, IsSpellKnown) @@ -1454,6 +1458,13 @@ function Addon:GetInternalBindingType(binding) return binding.type end +--- @param bindings Binding[] +--- @param interactionType number +--- @return string +function Addon:GetMacroName(bindings, interactionType) + return "clicked-" .. bindings[1].uid .. "-" .. interactionType +end + --- Construct a valid macro that correctly prioritizes all specified bindings. --- It will prioritize bindings in the following order: --- @@ -1484,7 +1495,9 @@ function Addon:GetMacroForBindings(bindings, interactionType) assert(type(interactionType) == "number", "bad argument #1, expected number but got " .. type(interactionType)) --- @type string[] - local lines = {} + local lines = { + "#showtooltip" + } --- @type string[] local macroConditions = {} diff --git a/Clicked/Core/CommandProcessor.lua b/Clicked/Core/CommandProcessor.lua index c578c770..3fb1ae70 100644 --- a/Clicked/Core/CommandProcessor.lua +++ b/Clicked/Core/CommandProcessor.lua @@ -135,6 +135,24 @@ local function EnsureMacroFrameHandler() Clicked:RegisterFrameClicks(macroFrameHandler, false) end +local function CreateOrUpdateMacro(name, body) + if GetMacroInfo(name) ~= nil then + EditMacro(name, name, "INV_Misc_QuestionMark", body) + else + CreateMacro(name, "INV_Misc_QuestionMark", body) + end +end + +local function DeleteUnusedMacros(usedMacroNames) + for i = GetNumMacros(), 1, -1 do + local name = GetMacroInfo(i) + + if string.find(name, "clicked-") and not tContains(usedMacroNames, name) then + DeleteMacro(name) + end + end +end + -- Private addon API --- @param keybinds Keybind[] @@ -182,6 +200,9 @@ function Addon:ProcessCommands(commands) --- @type table local newMacroFrameHandlerAttributes = {} + --- @type string[] + local usedMacroNames = {} + EnsureMacroFrameHandler() -- Unregister all current keybinds @@ -199,6 +220,11 @@ function Addon:ProcessCommands(commands) identifier = command.suffix } + if command.action == Addon.CommandType.MACRO then + CreateOrUpdateMacro(command.macroName, command.data) + table.insert(usedMacroNames, command.macroName) + end + Addon:CreateCommandAttributes(attributes, command, command.prefix, command.suffix) if command.hovercast then @@ -227,6 +253,8 @@ function Addon:ProcessCommands(commands) end end + DeleteUnusedMacros(usedMacroNames) + Addon:StatusOutput_UpdateMacroHandlerAttributes(newMacroFrameHandlerAttributes) Addon:UpdateMacroFrameHandler(newMacroFrameHandlerKeybinds, newMacroFrameHandlerAttributes) diff --git a/Clicked/Core/Database.lua b/Clicked/Core/Database.lua index 950e7431..08b180a7 100644 --- a/Clicked/Core/Database.lua +++ b/Clicked/Core/Database.lua @@ -109,6 +109,7 @@ function Clicked:GetDatabaseDefaults() bindings = {}, nextGroupId = 1, nextBindingId = 1, + nextBindingUid = 1, keyVisualizer = { lastKeyboardLayout = nil, lastKeyboardSize = nil, @@ -168,27 +169,35 @@ end --- Delete a binding group. If the group is not empty, it will also delete all child-bindings. --- --- @param group Group +--- @returns boolean function Clicked:DeleteGroup(group) assert(type(group) == "table", "bad argument #1, expected table but got " .. type(group)) local db = Addon:GetContainingDatabase(group) + local deleted = false for i, e in ipairs(db.groups) do if e.identifier == group.identifier then table.remove(db.groups, i) + deleted = true break end end - for i = #db.bindings, 1, -1 do - local binding = db.bindings[i] + if deleted then + for i = #db.bindings, 1, -1 do + local binding = db.bindings[i] - if binding.parent == group.identifier then - table.remove(db.bindings, i) + if binding.parent == group.identifier then + table.remove(db.bindings, i) + end end + + self:ReloadBindings(true) + return true end - self:ReloadBindings(true) + return false end --- Attempt to get a binding group with the specified identifier. @@ -258,19 +267,27 @@ end --- bindings. --- --- @param binding Binding The binding to delete +--- @returns boolean function Clicked:DeleteBinding(binding) assert(type(binding) == "table", "bad argument #1, expected table but got " .. type(binding)) local db = Addon:GetContainingDatabase(binding) + local deleted = false for index, item in ipairs(db.bindings) do if binding.identifier == item.identifier then table.remove(db.bindings, index) + deleted = true break end end - self:ReloadBindings(true) + if deleted then + self:ReloadBindings(true) + return true + end + + return false end --- Iterate through all configured bindings, this will also include any bindings avaialble in the current profile that are not currently loaded. This function @@ -414,6 +431,12 @@ function Addon:GetNextBindingIdentifier(scope) return scope .. "-" .. BINDING_IDENTIFIER_PREFIX .. identifier, identifier end +function Addon:GetNextBindingUid() + local uid = Addon.db.global.nextBindingUid + Addon.db.global.nextBindingUid = uid + 1 + return uid +end + --- @param scope BindingScope --- @return string --- @return integer @@ -458,19 +481,21 @@ function Addon:ChangeScope(item, scope) local id = item.identifier local bindings = Clicked:GetBindingsInGroup(id) - Clicked:DeleteGroup(item) - self:RegisterGroup(item, scope) + if Clicked:DeleteGroup(item) then + self:RegisterGroup(item, scope) - for _, binding in ipairs(bindings) do - self:RegisterBinding(binding, scope) - binding.parent = item.identifier + for _, binding in ipairs(bindings) do + self:RegisterBinding(binding, scope) + binding.parent = item.identifier + end end else --- @cast item Binding - Clicked:DeleteBinding(item) - self:RegisterBinding(item, scope) - item.parent = nil + if Clicked:DeleteBinding(item) then + self:RegisterBinding(item, scope) + item.parent = nil + end end end @@ -495,6 +520,7 @@ function Addon:RegisterBinding(binding, scope) assert(type(binding) == "table", "bad argument #1, expected table but got " .. type(binding)) binding.identifier = self:GetNextBindingIdentifier(scope) + binding.uid = binding.uid or self:GetNextBindingUid() binding.scope = scope if scope == Addon.BindingScope.GLOBAL then diff --git a/Clicked/Core/Init.lua b/Clicked/Core/Init.lua index b72f76f0..4df1d034 100644 --- a/Clicked/Core/Init.lua +++ b/Clicked/Core/Init.lua @@ -29,6 +29,7 @@ Addon.EXPANSION = { BFA = 8, SL = 9, DF = 10, + TWW = 11, } --- @type ExpansionLevel @@ -111,7 +112,7 @@ function Addon:IsCata() end if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then - Addon.EXPANSION_LEVEL = Addon.EXPANSION.DF + Addon.EXPANSION_LEVEL = Addon.EXPANSION.TWW elseif WOW_PROJECT_ID == WOW_PROJECT_CATACLYSM_CLASSIC then Addon.EXPANSION_LEVEL = Addon.EXPANSION.CATA elseif WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC then diff --git a/Clicked/Core/LocaleUtils.lua b/Clicked/Core/LocaleUtils.lua index 40591d14..e9172f31 100644 --- a/Clicked/Core/LocaleUtils.lua +++ b/Clicked/Core/LocaleUtils.lua @@ -583,9 +583,12 @@ if Addon:IsGameVersionAtleast("RETAIL") then end for _, spellId in Addon:IterateShapeshiftForms(specId) do - local name, _, icon = Addon:GetSpellInfo(spellId) + local spell = Addon:GetSpellInfo(spellId) local key = #order + 1 + local icon = spell ~= nil and spell.iconID or nil + local name = spell ~= nil and spell.name or nil + items[key] = string.format("", icon, name) table.insert(order, key) end @@ -771,7 +774,10 @@ elseif Addon:IsGameVersionAtleast("CATA") then end end - local name, _, icon = Addon:GetSpellInfo(targetSpellId, false) + local spell = Addon:GetSpellInfo(targetSpellId, false) + local icon = spell ~= nil and spell.iconID or nil + local name = spell ~= nil and spell.name or nil + items[key] = string.format("", icon, name) table.insert(order, key) @@ -911,7 +917,10 @@ else end end - local name, _, icon = Addon:GetSpellInfo(targetSpellId, false) + local spell = Addon:GetSpellInfo(targetSpellId, false) + local icon = spell ~= nil and spell.iconID or nil + local name = spell ~= nil and spell.name or nil + items[key] = string.format("", icon, name) table.insert(order, key) diff --git a/Clicked/Core/Upgrader.lua b/Clicked/Core/Upgrader.lua index 03bd13cf..a9a902b4 100644 --- a/Clicked/Core/Upgrader.lua +++ b/Clicked/Core/Upgrader.lua @@ -3,7 +3,7 @@ --- @class ClickedInternal local Addon = select(2, ...) -Addon.DATA_VERSION = 7 +Addon.DATA_VERSION = 8 -- Local support functions @@ -819,6 +819,15 @@ local function Upgrade(db, from) end end end + + if from < 8 then + for _, binding in ipairs(db.bindings) do + binding.uid = Addon.db.global.nextBindingUid + Addon.db.global.nextBindingUid = Addon.db.global.nextBindingUid + 1 + end + + Addon:ShowInformationPopup("Clicked: Due to changes to the macro system in The War Within, Clicked is now required to use macro slots.\n\nAll your existing bindings will be automatically converted into macros and will use General Macro slots.") + end end -- Private addon API diff --git a/Clicked/Core/Utils.lua b/Clicked/Core/Utils.lua index 9ddc24be..bac5a2e4 100644 --- a/Clicked/Core/Utils.lua +++ b/Clicked/Core/Utils.lua @@ -413,8 +413,9 @@ function Addon:GetBindingValue(binding) assert(type(binding) == "table", "bad argument #1, expected table but got " .. type(binding)) if binding.type == Addon.BindingTypes.SPELL then - local spell = binding.action.spellValue - return self:GetSpellInfo(spell, binding.action.convertValueToId) or spell + local value = binding.action.spellValue + local spell = self:GetSpellInfo(value, binding.action.convertValueToId) + return spell ~= nil and spell.name ~= nil and spell.name or value end if binding.type == Addon.BindingTypes.ITEM then @@ -452,8 +453,12 @@ function Addon:GetSimpleSpellOrItemInfo(binding) assert(type(binding) == "table", "bad argument #1, expected table but got " .. type(binding)) if binding.type == Addon.BindingTypes.SPELL then - local name, _, icon, _, _, _, id = Addon:GetSpellInfo(binding.action.spellValue, binding.action.convertValueToId) - return name, icon, id + local spell = Addon:GetSpellInfo(binding.action.spellValue, binding.action.convertValueToId) + if spell == nil then + return nil, nil, nil + end + + return spell.name, spell.iconID, spell.spellID end if binding.type == Addon.BindingTypes.ITEM then @@ -467,8 +472,12 @@ function Addon:GetSimpleSpellOrItemInfo(binding) end if binding.type == Addon.BindingTypes.CANCELAURA then - local name, _, icon, _, _, _, id = Addon:GetSpellInfo(binding.action.auraName) - return name, icon, id + local spell = Addon:GetSpellInfo(binding.action.auraName) + if spell == nil then + return nil, nil, nil + end + + return spell.name, spell.iconID, spell.spellID end return nil, nil, nil @@ -583,17 +592,32 @@ end --- @param input string|integer --- @param addSubText? boolean ---- @return string name ---- @return nil rank ---- @return integer icon ---- @return number castTime ---- @return number minRange ---- @return number maxRange ---- @return integer spellId +--- @return SpellInfo? function Addon:GetSpellInfo(input, addSubText) assert(type(input) == "string" or type(input) == "number", "bad argument #1, expected string or number but got " .. type(input)) - local name, rank, icon, castTime, minRange, maxRange, spellId = GetSpellInfo(input) + local spell + + if Addon.EXPANSION_LEVEL >= Addon.EXPANSION.TWW then + spell = C_Spell.GetSpellInfo(input) + if spell == nil then + return nil + end + else + local name, _, icon, castTime, minRange, maxRange, spellId = GetSpellInfo(input) + if name == nil then + return nil + end + + spell = { + name = name, + iconID = icon, + castTime = castTime, + minRange = minRange, + maxRange = maxRange, + spellID = spellId + } + end if addSubText == nil then addSubText = true @@ -601,10 +625,10 @@ function Addon:GetSpellInfo(input, addSubText) if addSubText and Addon.EXPANSION_LEVEL <= Addon.EXPANSION.WOTLK then --- @diagnostic disable-next-line: redundant-parameter - local subtext = GetSpellSubtext(spellId) + local subtext = GetSpellSubtext(spell.spellID) if not self:IsStringNilOrEmpty(subtext) then - name = string.format("%s(%s)", name, subtext) + spell.name = string.format("%s(%s)", spell.name, subtext) end end @@ -613,14 +637,14 @@ function Addon:GetSpellInfo(input, addSubText) if Addon.EXPANSION_LEVEL >= Addon.EXPANSION.DF then local dragonRidingSpells = { 372608, 372610, 361584, 374990, 403092 } - if tContains(dragonRidingSpells, spellId) then + if tContains(dragonRidingSpells, spell.spellID) then --- @diagnostic disable-next-line: redundant-parameter - local subtext = GetSpellSubtext(spellId) or "Dragonriding" - name = string.format("%s(%s)", name, subtext) + local subtext = GetSpellSubtext(spell.spellID) or "Dragonriding" + spell.name = string.format("%s(%s)", spell.name, subtext) end end - return name, rank, icon, castTime, minRange, maxRange, spellId + return spell end --- Get the ID for the specified item name. @@ -644,11 +668,16 @@ end --- Get the spell ID for the specified spell name. --- --- @param name string ---- @return integer +--- @return integer? function Addon:GetSpellId(name) assert(type(name) == "string", "bad argument #1, expected string but got " .. type(name)) - return select(7, self:GetSpellInfo(name)) + local spell = self:GetSpellInfo(name) + if spell == nil then + return nil + end + + return spell.spellID end --- @param keybind string @@ -686,13 +715,13 @@ end --- --- For any keys that are _not_ mouse buttons, or the `hovercast` --- parameter has been set to `false`, a custom identifier will ---- be generated, generally prefixed by `clicked-mouse-` or ---- `clicked-button-` for mouse buttons and all other buttons respectively. +--- be generated, generally prefixed by `ccmbtn-` or `ccbtn-` +--- for mouse buttons and all other buttons respectively. --- ---- * `"T"` == `"", "clicked-button-t"` == `"type-clicked-button-t"` ---- * `"SHIFT-T"` == `"", "clicked-button-shiftt"` == `"type-clicked-button-shiftt"` ---- * `"BUTTON3"` == `"", "clicked-mouse-3"` == `"type-clicked-mouse-3"` ---- * `"SHIFT-BUTTON3"` == `"", "clicked-mouse-shift3"` == `"type-clicked-mouse-shift3"` +--- * `"T"` == `"", "ccbtn-t"` == `"type-ccbtn-t"` +--- * `"SHIFT-T"` == `"", "ccbtn-shiftt"` == `"type-ccbtn-shiftt"` +--- * `"BUTTON3"` == `"", "ccmbtn-3"` == `"type-ccmbtn-3"` +--- * `"SHIFT-BUTTON3"` == `"", "ccmbtn-shift3"` == `"type-ccmbtn-shift3"` --- --- @param keybind string --- @param hovercast boolean diff --git a/Clicked/Definitions.lua b/Clicked/Definitions.lua index bac9329f..2e08a1df 100644 --- a/Clicked/Definitions.lua +++ b/Clicked/Definitions.lua @@ -44,6 +44,7 @@ --- @class Binding --- @field public type string --- @field public identifier string +--- @field public uid integer --- @field public scope BindingScope --- @field public keybind string --- @field public parent string? @@ -168,6 +169,7 @@ --- @field public data any --- @field public prefix string --- @field public suffix string +--- @field public macroName string --- @class Keybind --- @field public key string diff --git a/Clicked/Libs/AceComm-3.0/AceComm-3.0.lua b/Clicked/Libs/AceComm-3.0/AceComm-3.0.lua index 3f21f5b7..1fc7a378 100644 --- a/Clicked/Libs/AceComm-3.0/AceComm-3.0.lua +++ b/Clicked/Libs/AceComm-3.0/AceComm-3.0.lua @@ -9,7 +9,7 @@ -- make into AceComm. -- @class file -- @name AceComm-3.0 --- @release $Id: AceComm-3.0.lua 1284 2022-09-25 09:15:30Z nevcairiel $ +-- @release $Id: AceComm-3.0.lua 1333 2024-05-05 16:24:39Z nevcairiel $ --[[ AceComm-3.0 @@ -20,7 +20,7 @@ TODO: Time out old data rotting around from dead senders? Not a HUGE deal since local CallbackHandler = LibStub("CallbackHandler-1.0") local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib") -local MAJOR, MINOR = "AceComm-3.0", 12 +local MAJOR, MINOR = "AceComm-3.0", 14 local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceComm then return end @@ -93,12 +93,12 @@ function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callb local textlen = #text local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327 - local queueName = prefix..distribution..(target or "") + local queueName = prefix local ctlCallback = nil if callbackFn then - ctlCallback = function(sent) - return callbackFn(callbackArg, sent, textlen) + ctlCallback = function(sent, sendResult) + return callbackFn(callbackArg, sent, textlen, sendResult) end end diff --git a/Clicked/Libs/AceComm-3.0/ChatThrottleLib.lua b/Clicked/Libs/AceComm-3.0/ChatThrottleLib.lua index d1dd8a05..688d3181 100644 --- a/Clicked/Libs/AceComm-3.0/ChatThrottleLib.lua +++ b/Clicked/Libs/AceComm-3.0/ChatThrottleLib.lua @@ -23,7 +23,7 @@ -- LICENSE: ChatThrottleLib is released into the Public Domain -- -local CTL_VERSION = 24 +local CTL_VERSION = 29 local _G = _G @@ -74,9 +74,7 @@ local math_max = math.max local next = next local strlen = string.len local GetFramerate = GetFramerate -local strlower = string.lower local unpack,type,pairs,wipe = unpack,type,pairs,table.wipe -local UnitInRaid,UnitInParty = UnitInRaid,UnitInParty ----------------------------------------------------------------------- @@ -115,6 +113,23 @@ function Ring:Remove(obj) end end +-- Note that this is local because there's no upgrade logic for existing ring +-- metatables, and this isn't present on rings created in versions older than +-- v25. +local function Ring_Link(self, other) -- Move and append all contents of another ring to this ring + if not self.pos then + -- This ring is empty, so just transfer ownership. + self.pos = other.pos + other.pos = nil + elseif other.pos then + -- Our tail should point to their head, and their tail to our head. + self.pos.prev.next, other.pos.prev.next = other.pos, self.pos + -- Our head should point to their tail, and their head to our tail. + self.pos.prev, other.pos.prev = other.pos.prev, self.pos.prev + other.pos = nil + end +end + ----------------------------------------------------------------------- @@ -179,6 +194,13 @@ function ChatThrottleLib:Init() self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 } end + if not self.BlockedQueuesDelay then + -- v25: Add blocked queues to rings to handle new client throttles. + for _, Prio in pairs(self.Prio) do + Prio.Blocked = Ring:New() + end + end + -- v4: total send counters per priority for _, Prio in pairs(self.Prio) do Prio.nTotalSent = Prio.nTotalSent or 0 @@ -201,6 +223,7 @@ function ChatThrottleLib:Init() self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD") self.OnUpdateDelay = 0 + self.BlockedQueuesDelay = 0 self.LastAvailUpdate = GetTime() self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup @@ -213,16 +236,27 @@ function ChatThrottleLib:Init() return ChatThrottleLib.Hook_SendChatMessage(...) end) --SendAddonMessage - if _G.C_ChatInfo then - hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...) - return ChatThrottleLib.Hook_SendAddonMessage(...) - end) - else - hooksecurefunc("SendAddonMessage", function(...) - return ChatThrottleLib.Hook_SendAddonMessage(...) - end) - end + hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...) + return ChatThrottleLib.Hook_SendAddonMessage(...) + end) + end + + -- v26: Hook SendAddonMessageLogged for traffic logging + if not self.securelyHookedLogged then + self.securelyHookedLogged = true + hooksecurefunc(_G.C_ChatInfo, "SendAddonMessageLogged", function(...) + return ChatThrottleLib.Hook_SendAddonMessageLogged(...) + end) + end + + -- v29: Hook BNSendGameData for traffic logging + if not self.securelyHookedBNGameData then + self.securelyHookedBNGameData = true + hooksecurefunc("BNSendGameData", function(...) + return ChatThrottleLib.Hook_BNSendGameData(...) + end) end + self.nBypass = 0 end @@ -251,6 +285,12 @@ function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destinati self.avail = self.avail - size self.nBypass = self.nBypass + size -- just a statistic end +function ChatThrottleLib.Hook_SendAddonMessageLogged(prefix, text, chattype, destination, ...) + ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...) +end +function ChatThrottleLib.Hook_BNSendGameData(destination, prefix, text) + ChatThrottleLib.Hook_SendAddonMessage(prefix, text, "WHISPER", destination) +end @@ -292,38 +332,89 @@ end -- - ... made up of N "Pipe"s (1 for each destination/pipename) -- - and each pipe contains messages +local SendAddonMessageResult = Enum.SendAddonMessageResult or { + Success = 0, + AddonMessageThrottle = 3, + NotInGroup = 5, + ChannelThrottle = 8, + GeneralError = 9, +} + +local function MapToSendResult(ok, ...) + local result + + if not ok then + -- The send function itself errored; don't look at anything else. + result = SendAddonMessageResult.GeneralError + else + -- Grab the last return value from the send function and remap + -- it from a boolean to an enum code. If there are no results, + -- assume success (true). + + result = select(-1, true, ...) + + if result == true then + result = SendAddonMessageResult.Success + elseif result == false then + result = SendAddonMessageResult.GeneralError + end + end + + return result +end + +local function IsThrottledSendResult(result) + return result == SendAddonMessageResult.AddonMessageThrottle +end + +-- A copy of this function exists in FrameXML, but for clarity it's here too. +local function CallErrorHandler(...) + return geterrorhandler()(...) +end + +local function PerformSend(sendFunction, ...) + bMyTraffic = true + local sendResult = MapToSendResult(xpcall(sendFunction, CallErrorHandler, ...)) + bMyTraffic = false + return sendResult +end + function ChatThrottleLib:Despool(Prio) local ring = Prio.Ring while ring.pos and Prio.avail > ring.pos[1].nSize do - local msg = table_remove(ring.pos, 1) - if not ring.pos[1] then -- did we remove last msg in this pipe? - local pipe = Prio.Ring.pos + local pipe = ring.pos + local msg = pipe[1] + local sendResult = PerformSend(msg.f, unpack(msg, 1, msg.n)) + + if IsThrottledSendResult(sendResult) then + -- Message was throttled; move the pipe into the blocked ring. Prio.Ring:Remove(pipe) - Prio.ByName[pipe.name] = nil - DelPipe(pipe) - else - Prio.Ring.pos = Prio.Ring.pos.next - end - local didSend=false - local lowerDest = strlower(msg[3] or "") - if lowerDest == "raid" and not UnitInRaid("player") then - -- do nothing - elseif lowerDest == "party" and not UnitInParty("player") then - -- do nothing + Prio.Blocked:Add(pipe) else - Prio.avail = Prio.avail - msg.nSize - bMyTraffic = true - msg.f(unpack(msg, 1, msg.n)) - bMyTraffic = false - Prio.nTotalSent = Prio.nTotalSent + msg.nSize + -- Dequeue message after submission. + table_remove(pipe, 1) DelMsg(msg) - didSend = true - end - -- notify caller of delivery (even if we didn't send it) - if msg.callbackFn then - msg.callbackFn (msg.callbackArg, didSend) + + if not pipe[1] then -- did we remove last msg in this pipe? + Prio.Ring:Remove(pipe) + Prio.ByName[pipe.name] = nil + DelPipe(pipe) + else + ring.pos = ring.pos.next + end + + -- Update bandwidth counters on successful sends. + local didSend = (sendResult == SendAddonMessageResult.Success) + if didSend then + Prio.avail = Prio.avail - msg.nSize + Prio.nTotalSent = Prio.nTotalSent + msg.nSize + end + + -- Notify caller of message submission. + if msg.callbackFn then + securecallfunction(msg.callbackFn, msg.callbackArg, didSend, sendResult) + end end - -- USER CALLBACK MAY ERROR end end @@ -342,6 +433,7 @@ function ChatThrottleLib.OnUpdate(this,delay) local self = ChatThrottleLib self.OnUpdateDelay = self.OnUpdateDelay + delay + self.BlockedQueuesDelay = self.BlockedQueuesDelay + delay if self.OnUpdateDelay < 0.08 then return end @@ -353,40 +445,60 @@ function ChatThrottleLib.OnUpdate(this,delay) return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu. end - -- See how many of our priorities have queued messages (we only have 3, don't worry about the loop) - local n = 0 - for prioname,Prio in pairs(self.Prio) do - if Prio.Ring.pos or Prio.avail < 0 then - n = n + 1 + -- Integrate blocked queues back into their rings periodically. + if self.BlockedQueuesDelay >= 0.35 then + for _, Prio in pairs(self.Prio) do + Ring_Link(Prio.Ring, Prio.Blocked) end + + self.BlockedQueuesDelay = 0 end - -- Anything queued still? - if n<1 then - -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing - for prioname, Prio in pairs(self.Prio) do + -- See how many of our priorities have queued messages. This is split + -- into two counters because priorities that consist only of blocked + -- queues must keep our OnUpdate alive, but shouldn't count toward + -- bandwidth distribution. + local nSendablePrios = 0 + local nBlockedPrios = 0 + + for prioname, Prio in pairs(self.Prio) do + if Prio.Ring.pos then + nSendablePrios = nSendablePrios + 1 + elseif Prio.Blocked.pos then + nBlockedPrios = nBlockedPrios + 1 + end + + -- Collect unused bandwidth from priorities with nothing to send. + if not Prio.Ring.pos then self.avail = self.avail + Prio.avail Prio.avail = 0 end - self.bQueueing = false - self.Frame:Hide() + end + + -- Bandwidth reclamation may take us back over the burst cap. + self.avail = math_min(self.avail, self.BURST) + + -- If we can't currently send on any priorities, stop processing early. + if nSendablePrios == 0 then + -- If we're completely out of data to send, disable queue processing. + if nBlockedPrios == 0 then + self.bQueueing = false + self.Frame:Hide() + end + return end -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues - local avail = self.avail/n + local avail = self.avail / nSendablePrios self.avail = 0 for prioname, Prio in pairs(self.Prio) do - if Prio.Ring.pos or Prio.avail < 0 then + if Prio.Ring.pos then Prio.avail = Prio.avail + avail - if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then - self:Despool(Prio) - -- Note: We might not get here if the user-supplied callback function errors out! Take care! - end + self:Despool(Prio) end end - end @@ -429,16 +541,22 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag -- Check if there's room in the global available bandwidth gauge to send directly if not self.bQueueing and nSize < self:UpdateAvail() then - self.avail = self.avail - nSize - bMyTraffic = true - _G.SendChatMessage(text, chattype, language, destination) - bMyTraffic = false - self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize - if callbackFn then - callbackFn (callbackArg, true) + local sendResult = PerformSend(_G.SendChatMessage, text, chattype, language, destination) + + if not IsThrottledSendResult(sendResult) then + local didSend = (sendResult == SendAddonMessageResult.Success) + + if didSend then + self.avail = self.avail - nSize + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + end + + if callbackFn then + securecallfunction(callbackFn, callbackArg, didSend, sendResult) + end + + return end - -- USER CALLBACK MAY ERROR - return end -- Message needs to be queued @@ -453,54 +571,36 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag msg.callbackFn = callbackFn msg.callbackArg = callbackArg - self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg) + self:Enqueue(prio, queueName or prefix, msg) end -function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) - if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then - error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) - end - if callbackFn and type(callbackFn)~="function" then - error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) - end +local function SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + local nSize = #text + self.MSG_OVERHEAD - local nSize = text:len(); + -- Check if there's room in the global available bandwidth gauge to send directly + if not self.bQueueing and nSize < self:UpdateAvail() then + local sendResult = PerformSend(sendFunction, prefix, text, chattype, target) - if C_ChatInfo or RegisterAddonMessagePrefix then - if nSize>255 then - error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2) - end - else - nSize = nSize + prefix:len() + 1 - if nSize>255 then - error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2) - end - end + if not IsThrottledSendResult(sendResult) then + local didSend = (sendResult == SendAddonMessageResult.Success) + + if didSend then + self.avail = self.avail - nSize + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + end - nSize = nSize + self.MSG_OVERHEAD; + if callbackFn then + securecallfunction(callbackFn, callbackArg, didSend, sendResult) + end - -- Check if there's room in the global available bandwidth gauge to send directly - if not self.bQueueing and nSize < self:UpdateAvail() then - self.avail = self.avail - nSize - bMyTraffic = true - if _G.C_ChatInfo then - _G.C_ChatInfo.SendAddonMessage(prefix, text, chattype, target) - else - _G.SendAddonMessage(prefix, text, chattype, target) - end - bMyTraffic = false - self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize - if callbackFn then - callbackFn (callbackArg, true) + return end - -- USER CALLBACK MAY ERROR - return end -- Message needs to be queued local msg = NewMsg() - msg.f = _G.C_ChatInfo and _G.C_ChatInfo.SendAddonMessage or _G.SendAddonMessage + msg.f = sendFunction msg[1] = prefix msg[2] = text msg[3] = chattype @@ -510,10 +610,64 @@ function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, msg.callbackFn = callbackFn msg.callbackArg = callbackArg - self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg) + self:Enqueue(prio, queueName or prefix, msg) +end + + +function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2) + end + + local sendFunction = _G.C_ChatInfo.SendAddonMessage + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) end +function ChatThrottleLib:SendAddonMessageLogged(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendAddonMessageLogged("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:SendAddonMessageLogged(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:SendAddonMessageLogged(): message length cannot exceed 255 bytes", 2) + end + + local sendFunction = _G.C_ChatInfo.SendAddonMessageLogged + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) +end + +local function BNSendGameDataReordered(prefix, text, _, gameAccountID) + return _G.BNSendGameData(gameAccountID, prefix, text) +end + +function ChatThrottleLib:BNSendGameData(prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg) + -- Note that this API is intentionally limited to 255 bytes of data + -- for reasons of traffic fairness, which is less than the 4078 bytes + -- BNSendGameData natively supports. Additionally, a chat type is required + -- but must always be set to 'WHISPER' to match what is exposed by the + -- receipt event. + -- + -- If splitting messages, callers must also be aware that message + -- delivery over BNSendGameData is unordered. + + if not self or not prio or not prefix or not text or not gameAccountID or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:BNSendGameData("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype", gameAccountID)', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:BNSendGameData(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:BNSendGameData(): message length cannot exceed 255 bytes", 2) + elseif chattype ~= "WHISPER" then + error("ChatThrottleLib:BNSendGameData(): chat type must be 'WHISPER'", 2) + end + + local sendFunction = BNSendGameDataReordered + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg) +end ----------------------------------------------------------------------- diff --git a/Clicked/Libs/AceConfig-3.0/AceConfig-3.0.lua b/Clicked/Libs/AceConfig-3.0/AceConfig-3.0.lua index 5071cdcf..ab91c9e1 100644 --- a/Clicked/Libs/AceConfig-3.0/AceConfig-3.0.lua +++ b/Clicked/Libs/AceConfig-3.0/AceConfig-3.0.lua @@ -3,7 +3,7 @@ -- as well as associate it with a slash command. -- @class file -- @name AceConfig-3.0 --- @release $Id: AceConfig-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $ +-- @release $Id: AceConfig-3.0.lua 1335 2024-05-05 19:35:16Z nevcairiel $ --[[ AceConfig-3.0 @@ -27,7 +27,7 @@ if not AceConfig then return end local pcall, error, type, pairs = pcall, error, type, pairs -- ------------------------------------------------------------------- --- :RegisterOptionsTable(appName, options, slashcmd, persist) +-- :RegisterOptionsTable(appName, options, slashcmd) -- -- - appName - (string) application name -- - options - table or function ref, see AceConfigRegistry diff --git a/Clicked/Libs/AceDB-3.0/AceDB-3.0.lua b/Clicked/Libs/AceDB-3.0/AceDB-3.0.lua index a8e306a3..1e254293 100644 --- a/Clicked/Libs/AceDB-3.0/AceDB-3.0.lua +++ b/Clicked/Libs/AceDB-3.0/AceDB-3.0.lua @@ -40,8 +40,8 @@ -- end -- @class file -- @name AceDB-3.0.lua --- @release $Id: AceDB-3.0.lua 1306 2023-06-23 14:55:09Z nevcairiel $ -local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 28 +-- @release $Id: AceDB-3.0.lua 1328 2024-03-20 22:36:27Z nevcairiel $ +local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 29 local AceDB = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) if not AceDB then return end -- No upgrade needed @@ -606,8 +606,8 @@ end -- profile. -- @param defaultProfile The profile name to use as the default function DBObjectLib:ResetDB(defaultProfile) - if defaultProfile and type(defaultProfile) ~= "string" then - error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected, got %q."):format(type(defaultProfile)), 2) + if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then + error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2) end local sv = self.sv diff --git a/Clicked/Libs/LibMacroSyntaxHighlight-1.0/LibMacroSyntaxHighlight.lua b/Clicked/Libs/LibMacroSyntaxHighlight-1.0/LibMacroSyntaxHighlight.lua index cad33570..e29cc3dc 100644 --- a/Clicked/Libs/LibMacroSyntaxHighlight-1.0/LibMacroSyntaxHighlight.lua +++ b/Clicked/Libs/LibMacroSyntaxHighlight-1.0/LibMacroSyntaxHighlight.lua @@ -31,7 +31,8 @@ Library.TokenType = { SPELL_NAME = 10, SLASH_COMMAND = 11, CONDITION = 12, - TARGET_UNIT = 13 + TARGET_UNIT = 13, + SHEBANG = 14, } --- @type LibMacroSyntaxHighlight-1.0.ColorTable @@ -46,7 +47,8 @@ local defaultColorTable = { [Library.TokenType.SPELL_NAME] = "ffce9178", [Library.TokenType.SLASH_COMMAND] = "ffdcdcaa", [Library.TokenType.CONDITION] = "ff9cdcfe", - [Library.TokenType.TARGET_UNIT] = "ff4ec9b0" + [Library.TokenType.TARGET_UNIT] = "ff4ec9b0", + [Library.TokenType.SHEBANG] = "ffffffff", } local bytes = { @@ -62,7 +64,8 @@ local bytes = { ["SLASH"] = string.byte("/"), ["NUM_0"] = string.byte("0"), ["NUM_9"] = string.byte("9"), - ["AT"] = string.byte("@") + ["AT"] = string.byte("@"), + ["SHEBANG"] = string.byte("#") } local linebreaks = { @@ -186,6 +189,15 @@ local function GetNextToken(text, position, state) end end end + elseif byte == bytes.SHEBANG and IsStartOfLine(text, position) then + while true do + position = position + 1 + byte = string.byte(text, position) + + if byte == nil or IsSpecialByte(byte) then + return Library.TokenType.SHEBANG, position + end + end end end diff --git a/Clicked/Widgets/AceGUIContainer-ClickedTreeGroup.lua b/Clicked/Widgets/AceGUIContainer-ClickedTreeGroup.lua index 4bcb65fd..5fbe38e2 100644 --- a/Clicked/Widgets/AceGUIContainer-ClickedTreeGroup.lua +++ b/Clicked/Widgets/AceGUIContainer-ClickedTreeGroup.lua @@ -107,6 +107,26 @@ local contextMenuFrame = CreateFrame("Frame", "ClickedContextMenu", UIParent, "U Support functions -------------------------------------------------------------------------------]] +local function EasyMenu_Initialize(_, level, menuList) + for index = 1, #menuList do + local value = menuList[index] + + if value.text then + value.index = index; + UIDropDownMenu_AddButton(value, level); + end + end +end + +local function EasyMenu(menuList, menuFrame, anchor, x, y, displayMode, autoHideDelay) + if displayMode == "MENU" then + menuFrame.displayMode = displayMode; + end + + UIDropDownMenu_Initialize(menuFrame, EasyMenu_Initialize, displayMode, nil, menuList); + ToggleDropDownMenu(1, nil, menuFrame, anchor, x, y, menuList, nil, autoHideDelay); +end + --- @param left ClickedTreeGroup.Item --- @param right ClickedTreeGroup.Item --- @return boolean @@ -620,6 +640,7 @@ local function Button_OnClick(frame, button) end }) + -- TODO: Rewrite this to not use EasyMenu when it's propegated to all clients EasyMenu(menu, contextMenuFrame, frame, 0, 0, "MENU") end end diff --git a/ClickedMedia/ClickedMedia.toc b/ClickedMedia/ClickedMedia.toc index a2008739..c216d360 100644 --- a/ClickedMedia/ClickedMedia.toc +++ b/ClickedMedia/ClickedMedia.toc @@ -1,4 +1,4 @@ -## Interface: 100207 +## Interface: 110000 ## Title: ClickedMedia ## IconTexture: Interface\Icons\inv_misc_punchcards_yellow ## Author: Snakybo