diff --git a/Changes.txt b/Changes.txt deleted file mode 100644 index 9bec14e..0000000 --- a/Changes.txt +++ /dev/null @@ -1,11 +0,0 @@ -Many bug fixes, new profession functionality, refactoring. - -- Now able to show all professions including trainer-taught skills as well as recipes, formulas etc. -- Minor, minor UI adjustments. - -Bug fixes: -- Fixed bug where Hunter pet skills and Warlock demon spells do not show as learned. -- The Mail and Plate skills will now show as learned once they have been learned. -- Rogue poisons will now show as learned once they have been learned. -- Dire Bear Form used to show Bear Form as learnable again (due to the player technically unlearning Bear Form). This has now been fixed. -- Fixed a bug where certain classes' spells did not show up. \ No newline at end of file diff --git a/FieldGuide.toc b/FieldGuide.toc index 9d92966..295dedb 100644 --- a/FieldGuide.toc +++ b/FieldGuide.toc @@ -1,4 +1,4 @@ -## Interface: 11305 +## Interface: 11401 ## Title: Field Guide ## Notes: Shows when spells are trainable and how much each spell costs to train, and other stuff. ## Author: cloudbells diff --git a/FieldGuide.xml b/FieldGuide.xml index b59cd6b..f7c5bf6 100644 --- a/FieldGuide.xml +++ b/FieldGuide.xml @@ -259,6 +259,10 @@ FieldGuide_OnLoad(self) + FieldGuideFrame:SetBackdrop({ + edgeFile = "Interface/DialogFrame/UI-DialogBox-Border", + edgeSize = 32, + }) PlaySound(SOUNDKIT.IG_SPELLBOOK_OPEN) diff --git a/Libraries/CallbackHandler-1.0/CallbackHandler-1.0.lua b/Libraries/CallbackHandler-1.0/CallbackHandler-1.0.lua index 39e265d..4c9a302 100644 --- a/Libraries/CallbackHandler-1.0/CallbackHandler-1.0.lua +++ b/Libraries/CallbackHandler-1.0/CallbackHandler-1.0.lua @@ -1,212 +1,212 @@ ---[[ $Id: CallbackHandler-1.0.lua 22 2018-07-21 14:17:22Z nevcairiel $ ]] -local MAJOR, MINOR = "CallbackHandler-1.0", 7 -local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) - -if not CallbackHandler then return end -- No upgrade needed - -local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} - --- Lua APIs -local tconcat = table.concat -local assert, error, loadstring = assert, error, loadstring -local setmetatable, rawset, rawget = setmetatable, rawset, rawget -local next, select, pairs, type, tostring = next, select, pairs, type, tostring - --- Global vars/functions that we don't upvalue since they might get hooked, or upgraded --- List them here for Mikk's FindGlobals script --- GLOBALS: geterrorhandler - -local xpcall = xpcall - -local function errorhandler(err) - return geterrorhandler()(err) -end - -local function Dispatch(handlers, ...) - local index, method = next(handlers) - if not method then return end - repeat - xpcall(method, errorhandler, ...) - index, method = next(handlers, index) - until not method -end - --------------------------------------------------------------------------- --- CallbackHandler:New --- --- target - target object to embed public APIs in --- RegisterName - name of the callback registration API, default "RegisterCallback" --- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" --- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. - -function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName) - - RegisterName = RegisterName or "RegisterCallback" - UnregisterName = UnregisterName or "UnregisterCallback" - if UnregisterAllName==nil then -- false is used to indicate "don't want this method" - UnregisterAllName = "UnregisterAllCallbacks" - end - - -- we declare all objects and exported APIs inside this closure to quickly gain access - -- to e.g. function names, the "target" parameter, etc - - - -- Create the registry object - local events = setmetatable({}, meta) - local registry = { recurse=0, events=events } - - -- registry:Fire() - fires the given event/message into the registry - function registry:Fire(eventname, ...) - if not rawget(events, eventname) or not next(events[eventname]) then return end - local oldrecurse = registry.recurse - registry.recurse = oldrecurse + 1 - - Dispatch(events[eventname], eventname, ...) - - registry.recurse = oldrecurse - - if registry.insertQueue and oldrecurse==0 then - -- Something in one of our callbacks wanted to register more callbacks; they got queued - for eventname,callbacks in pairs(registry.insertQueue) do - local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. - for self,func in pairs(callbacks) do - events[eventname][self] = func - -- fire OnUsed callback? - if first and registry.OnUsed then - registry.OnUsed(registry, target, eventname) - first = nil - end - end - end - registry.insertQueue = nil - end - end - - -- Registration of a callback, handles: - -- self["method"], leads to self["method"](self, ...) - -- self with function ref, leads to functionref(...) - -- "addonId" (instead of self) with function ref, leads to functionref(...) - -- all with an optional arg, which, if present, gets passed as first argument (after self if present) - target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) - if type(eventname) ~= "string" then - error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) - end - - method = method or eventname - - local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. - - if type(method) ~= "string" and type(method) ~= "function" then - error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) - end - - local regfunc - - if type(method) == "string" then - -- self["method"] calling style - if type(self) ~= "table" then - error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) - elseif self==target then - error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) - elseif type(self[method]) ~= "function" then - error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) - end - - if select("#",...)>=1 then -- this is not the same as testing for arg==nil! - local arg=select(1,...) - regfunc = function(...) self[method](self,arg,...) end - else - regfunc = function(...) self[method](self,...) end - end - else - -- function ref with self=object or self="addonId" or self=thread - if type(self)~="table" and type(self)~="string" and type(self)~="thread" then - error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2) - end - - if select("#",...)>=1 then -- this is not the same as testing for arg==nil! - local arg=select(1,...) - regfunc = function(...) method(arg,...) end - else - regfunc = method - end - end - - - if events[eventname][self] or registry.recurse<1 then - -- if registry.recurse<1 then - -- we're overwriting an existing entry, or not currently recursing. just set it. - events[eventname][self] = regfunc - -- fire OnUsed callback? - if registry.OnUsed and first then - registry.OnUsed(registry, target, eventname) - end - else - -- we're currently processing a callback in this registry, so delay the registration of this new entry! - -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency - registry.insertQueue = registry.insertQueue or setmetatable({},meta) - registry.insertQueue[eventname][self] = regfunc - end - end - - -- Unregister a callback - target[UnregisterName] = function(self, eventname) - if not self or self==target then - error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) - end - if type(eventname) ~= "string" then - error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) - end - if rawget(events, eventname) and events[eventname][self] then - events[eventname][self] = nil - -- Fire OnUnused callback? - if registry.OnUnused and not next(events[eventname]) then - registry.OnUnused(registry, target, eventname) - end - end - if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then - registry.insertQueue[eventname][self] = nil - end - end - - -- OPTIONAL: Unregister all callbacks for given selfs/addonIds - if UnregisterAllName then - target[UnregisterAllName] = function(...) - if select("#",...)<1 then - error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) - end - if select("#",...)==1 and ...==target then - error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) - end - - - for i=1,select("#",...) do - local self = select(i,...) - if registry.insertQueue then - for eventname, callbacks in pairs(registry.insertQueue) do - if callbacks[self] then - callbacks[self] = nil - end - end - end - for eventname, callbacks in pairs(events) do - if callbacks[self] then - callbacks[self] = nil - -- Fire OnUnused callback? - if registry.OnUnused and not next(callbacks) then - registry.OnUnused(registry, target, eventname) - end - end - end - end - end - end - - return registry -end - - --- CallbackHandler purposefully does NOT do explicit embedding. Nor does it --- try to upgrade old implicit embeds since the system is selfcontained and --- relies on closures to work. - +--[[ $Id: CallbackHandler-1.0.lua 22 2018-07-21 14:17:22Z nevcairiel $ ]] +local MAJOR, MINOR = "CallbackHandler-1.0", 7 +local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) + +if not CallbackHandler then return end -- No upgrade needed + +local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} + +-- Lua APIs +local tconcat = table.concat +local assert, error, loadstring = assert, error, loadstring +local setmetatable, rawset, rawget = setmetatable, rawset, rawget +local next, select, pairs, type, tostring = next, select, pairs, type, tostring + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: geterrorhandler + +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function Dispatch(handlers, ...) + local index, method = next(handlers) + if not method then return end + repeat + xpcall(method, errorhandler, ...) + index, method = next(handlers, index) + until not method +end + +-------------------------------------------------------------------------- +-- CallbackHandler:New +-- +-- target - target object to embed public APIs in +-- RegisterName - name of the callback registration API, default "RegisterCallback" +-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" +-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. + +function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName) + + RegisterName = RegisterName or "RegisterCallback" + UnregisterName = UnregisterName or "UnregisterCallback" + if UnregisterAllName==nil then -- false is used to indicate "don't want this method" + UnregisterAllName = "UnregisterAllCallbacks" + end + + -- we declare all objects and exported APIs inside this closure to quickly gain access + -- to e.g. function names, the "target" parameter, etc + + + -- Create the registry object + local events = setmetatable({}, meta) + local registry = { recurse=0, events=events } + + -- registry:Fire() - fires the given event/message into the registry + function registry:Fire(eventname, ...) + if not rawget(events, eventname) or not next(events[eventname]) then return end + local oldrecurse = registry.recurse + registry.recurse = oldrecurse + 1 + + Dispatch(events[eventname], eventname, ...) + + registry.recurse = oldrecurse + + if registry.insertQueue and oldrecurse==0 then + -- Something in one of our callbacks wanted to register more callbacks; they got queued + for eventname,callbacks in pairs(registry.insertQueue) do + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + for self,func in pairs(callbacks) do + events[eventname][self] = func + -- fire OnUsed callback? + if first and registry.OnUsed then + registry.OnUsed(registry, target, eventname) + first = nil + end + end + end + registry.insertQueue = nil + end + end + + -- Registration of a callback, handles: + -- self["method"], leads to self["method"](self, ...) + -- self with function ref, leads to functionref(...) + -- "addonId" (instead of self) with function ref, leads to functionref(...) + -- all with an optional arg, which, if present, gets passed as first argument (after self if present) + target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) + if type(eventname) ~= "string" then + error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) + end + + method = method or eventname + + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + + if type(method) ~= "string" and type(method) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) + end + + local regfunc + + if type(method) == "string" then + -- self["method"] calling style + if type(self) ~= "table" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) + elseif self==target then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) + elseif type(self[method]) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) self[method](self,arg,...) end + else + regfunc = function(...) self[method](self,...) end + end + else + -- function ref with self=object or self="addonId" or self=thread + if type(self)~="table" and type(self)~="string" and type(self)~="thread" then + error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) method(arg,...) end + else + regfunc = method + end + end + + + if events[eventname][self] or registry.recurse<1 then + -- if registry.recurse<1 then + -- we're overwriting an existing entry, or not currently recursing. just set it. + events[eventname][self] = regfunc + -- fire OnUsed callback? + if registry.OnUsed and first then + registry.OnUsed(registry, target, eventname) + end + else + -- we're currently processing a callback in this registry, so delay the registration of this new entry! + -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency + registry.insertQueue = registry.insertQueue or setmetatable({},meta) + registry.insertQueue[eventname][self] = regfunc + end + end + + -- Unregister a callback + target[UnregisterName] = function(self, eventname) + if not self or self==target then + error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) + end + if type(eventname) ~= "string" then + error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) + end + if rawget(events, eventname) and events[eventname][self] then + events[eventname][self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(events[eventname]) then + registry.OnUnused(registry, target, eventname) + end + end + if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then + registry.insertQueue[eventname][self] = nil + end + end + + -- OPTIONAL: Unregister all callbacks for given selfs/addonIds + if UnregisterAllName then + target[UnregisterAllName] = function(...) + if select("#",...)<1 then + error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) + end + if select("#",...)==1 and ...==target then + error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) + end + + + for i=1,select("#",...) do + local self = select(i,...) + if registry.insertQueue then + for eventname, callbacks in pairs(registry.insertQueue) do + if callbacks[self] then + callbacks[self] = nil + end + end + end + for eventname, callbacks in pairs(events) do + if callbacks[self] then + callbacks[self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(callbacks) then + registry.OnUnused(registry, target, eventname) + end + end + end + end + end + end + + return registry +end + + +-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it +-- try to upgrade old implicit embeds since the system is selfcontained and +-- relies on closures to work. + diff --git a/Libraries/HereBeDragons-2.0/HereBeDragons-2.0.lua b/Libraries/HereBeDragons-2.0/HereBeDragons-2.0.lua index bbfdad5..5be7c3e 100644 --- a/Libraries/HereBeDragons-2.0/HereBeDragons-2.0.lua +++ b/Libraries/HereBeDragons-2.0/HereBeDragons-2.0.lua @@ -1,534 +1,569 @@ --- HereBeDragons is a data API for the World of Warcraft mapping system - -local MAJOR, MINOR = "HereBeDragons-2.0", 9 -assert(LibStub, MAJOR .. " requires LibStub") - -local HereBeDragons, oldversion = LibStub:NewLibrary(MAJOR, MINOR) -if not HereBeDragons then return end - -local CBH = LibStub("CallbackHandler-1.0") - -HereBeDragons.eventFrame = HereBeDragons.eventFrame or CreateFrame("Frame") - -HereBeDragons.mapData = HereBeDragons.mapData or {} -HereBeDragons.worldMapData = HereBeDragons.worldMapData or {} -HereBeDragons.transforms = HereBeDragons.transforms or {} -HereBeDragons.callbacks = HereBeDragons.callbacks or CBH:New(HereBeDragons, nil, nil, false) - -local WoWClassic = select(4, GetBuildInfo()) < 20000 - --- Data Constants -local COSMIC_MAP_ID = 946 -local WORLD_MAP_ID = 947 - --- Lua upvalues -local PI2 = math.pi * 2 -local atan2 = math.atan2 -local pairs, ipairs = pairs, ipairs - --- WoW API upvalues -local UnitPosition = UnitPosition -local C_Map = C_Map - --- data table upvalues -local mapData = HereBeDragons.mapData -- table { width, height, left, top, .instance, .name, .mapType } -local worldMapData = HereBeDragons.worldMapData -- table { width, height, left, top } -local transforms = HereBeDragons.transforms - -local currentPlayerUIMapID, currentPlayerUIMapType - --- Override instance ids for phased content -local instanceIDOverrides = { - -- Draenor - [1152] = 1116, -- Horde Garrison 1 - [1330] = 1116, -- Horde Garrison 2 - [1153] = 1116, -- Horde Garrison 3 - [1154] = 1116, -- Horde Garrison 4 (unused) - [1158] = 1116, -- Alliance Garrison 1 - [1331] = 1116, -- Alliance Garrison 2 - [1159] = 1116, -- Alliance Garrison 3 - [1160] = 1116, -- Alliance Garrison 4 (unused) - [1191] = 1116, -- Ashran PvP Zone - [1203] = 1116, -- Frostfire Finale Scenario - [1207] = 1116, -- Talador Finale Scenario - [1277] = 1116, -- Defense of Karabor Scenario (SMV) - [1402] = 1116, -- Gorgrond Finale Scenario - [1464] = 1116, -- Tanaan - [1465] = 1116, -- Tanaan - -- Legion - [1478] = 1220, -- Temple of Elune Scenario (Val'Sharah) - [1495] = 1220, -- Protection Paladin Artifact Scenario (Stormheim) - [1498] = 1220, -- Havoc Demon Hunter Artifact Scenario (Suramar) - [1502] = 1220, -- Dalaran Underbelly - [1533] = 0, -- Karazhan Artifact Scenario - [1612] = 1220, -- Feral Druid Artifact Scenario (Suramar) - [1626] = 1220, -- Suramar Withered Scenario - [1662] = 1220, -- Suramar Invasion Scenario -} - --- gather map info, but only if this isn't an upgrade (or the upgrade version forces a re-map) -if not oldversion or oldversion < 7 then - -- wipe old data, if required, otherwise the upgrade path isn't triggered - if oldversion then - wipe(mapData) - wipe(worldMapData) - wipe(transforms) - end - - -- map transform data extracted from UIMapAssignment.db2 (see HereBeDragons-Scripts on GitHub) - -- format: instanceID, newInstanceID, minY, maxY, minX, maxX, offsetY, offsetX - local transformData = { - { 530, 0, 4800, 16000, -10133.3, -2666.67, -2400, 2400 }, - { 530, 1, -6933.33, 533.33, -16000, -8000, 10133.3, 17600 }, - { 732, 0, -3200, 533.3, -533.3, 2666.7, -611.8, 3904.3 }, - { 1064, 870, 5391, 8148, 3518, 7655, -2134.2, -2286.6 }, - { 1208, 1116, -2666, -2133, -2133, -1600, 10210, 2410 }, - { 1460, 1220, -1066.7, 2133.3, 0, 3200, -2333.9, 966.7 }, - } - - local function processTransforms() - for _, transform in pairs(transformData) do - local instanceID, newInstanceID, minY, maxY, minX, maxX, offsetY, offsetX = unpack(transform) - if not transforms[instanceID] then - transforms[instanceID] = {} - end - table.insert(transforms[instanceID], { newInstanceID = newInstanceID, minY = minY, maxY = maxY, minX = minX, maxX = maxX, offsetY = offsetY, offsetX = offsetX }) - end - end - - local function applyMapTransforms(instanceID, left, right, top, bottom) - if transforms[instanceID] then - for _, data in ipairs(transforms[instanceID]) do - if left <= data.maxX and right >= data.minX and top <= data.maxY and bottom >= data.minY then - instanceID = data.newInstanceID - left = left + data.offsetX - right = right + data.offsetX - top = top + data.offsetY - bottom = bottom + data.offsetY - break - end - end - end - return instanceID, left, right, top, bottom - end - - local vector00, vector05 = CreateVector2D(0, 0), CreateVector2D(0.5, 0.5) - -- gather the data of one map (by uiMapID) - local function processMap(id, data, parent) - if not id or not data or mapData[id] then return end - - if data.parentMapID and data.parentMapID ~= 0 then - parent = data.parentMapID - elseif not parent then - parent = 0 - end - - -- get two positions from the map, we use 0/0 and 0.5/0.5 to avoid issues on some maps where 1/1 is translated inaccurately - local instance, topLeft = C_Map.GetWorldPosFromMapPos(id, vector00) - local _, bottomRight = C_Map.GetWorldPosFromMapPos(id, vector05) - if topLeft and bottomRight then - local top, left = topLeft:GetXY() - local bottom, right = bottomRight:GetXY() - bottom = top + (bottom - top) * 2 - right = left + (right - left) * 2 - - instance, left, right, top, bottom = applyMapTransforms(instance, left, right, top, bottom) - mapData[id] = {left - right, top - bottom, left, top, instance = instance, name = data.name, mapType = data.mapType, parent = parent } - else - mapData[id] = {0, 0, 0, 0, instance = instance or -1, name = data.name, mapType = data.mapType, parent = parent } - end - end - - local function processMapChildrenRecursive(parent) - local children = C_Map.GetMapChildrenInfo(parent) - if children and #children > 0 then - for i = 1, #children do - local id = children[i].mapID - if id and not mapData[id] then - processMap(id, children[i], parent) - processMapChildrenRecursive(id) - - -- process sibling maps (in the same group) - -- in some cases these are not discovered by GetMapChildrenInfo above - local groupID = C_Map.GetMapGroupID(id) - if groupID then - local groupMembers = C_Map.GetMapGroupMembersInfo(groupID) - if groupMembers then - for k = 1, #groupMembers do - local memberId = groupMembers[k].mapID - if memberId and not mapData[memberId] then - processMap(memberId, C_Map.GetMapInfo(memberId), parent) - processMapChildrenRecursive(memberId) - end - end - end - end - end - end - end - end - - local function fixupZones() - local cosmic = C_Map.GetMapInfo(COSMIC_MAP_ID) - if cosmic then - mapData[COSMIC_MAP_ID] = {0, 0, 0, 0} - mapData[COSMIC_MAP_ID].instance = -1 - mapData[COSMIC_MAP_ID].name = cosmic.name - mapData[COSMIC_MAP_ID].mapType = cosmic.mapType - end - - -- data for the azeroth world map - if WoWClassic then - worldMapData[0] = { 44688.53, 29795.11, 32601.04, 9894.93 } - worldMapData[1] = { 44878.66, 29916.10, 8723.96, 14824.53 } - else - worldMapData[0] = { 76153.14, 50748.62, 65008.24, 23827.51 } - worldMapData[1] = { 77803.77, 51854.98, 13157.6, 28030.61 } - worldMapData[571] = { 71773.64, 50054.05, 36205.94, 12366.81 } - worldMapData[870] = { 67710.54, 45118.08, 33565.89, 38020.67 } - worldMapData[1220] = { 82758.64, 55151.28, 52943.46, 24484.72 } - worldMapData[1642] = { 77933.3, 51988.91, 44262.36, 32835.1 } - worldMapData[1643] = { 76060.47, 50696.96, 55384.8, 25774.35 } - end - end - - local function gatherMapData() - processTransforms() - - -- find all maps in well known structures - if WoWClassic then - processMap(WORLD_MAP_ID) - processMapChildrenRecursive(WORLD_MAP_ID) - else - processMapChildrenRecursive(COSMIC_MAP_ID) - end - - fixupZones() - - -- try to fill in holes in the map list - for i = 1, 2000 do - if not mapData[i] then - local mapInfo = C_Map.GetMapInfo(i) - if mapInfo and mapInfo.name then - processMap(i, mapInfo, nil) - end - end - end - end - - gatherMapData() -end - --- Transform a set of coordinates based on the defined map transformations -local function applyCoordinateTransforms(x, y, instanceID) - if transforms[instanceID] then - for _, transformData in ipairs(transforms[instanceID]) do - if transformData.minX <= x and transformData.maxX >= x and transformData.minY <= y and transformData.maxY >= y then - instanceID = transformData.newInstanceID - x = x + transformData.offsetX - y = y + transformData.offsetY - break - end - end - end - if instanceIDOverrides[instanceID] then - instanceID = instanceIDOverrides[instanceID] - end - return x, y, instanceID -end - -local StartUpdateTimer -local function UpdateCurrentPosition() - -- retrieve current zone - local uiMapID = C_Map.GetBestMapForUnit("player") - - if uiMapID ~= currentPlayerUIMapID then - -- update upvalues and signal callback - currentPlayerUIMapID, currentPlayerUIMapType = uiMapID, mapData[uiMapID] and mapData[uiMapID].mapType or 0 - HereBeDragons.callbacks:Fire("PlayerZoneChanged", currentPlayerUIMapID, currentPlayerUIMapType) - end - - -- start a timer to update in micro dungeons since multi-level micro dungeons do not reliably fire events - if currentPlayerUIMapType == Enum.UIMapType.Micro then - StartUpdateTimer() - end -end - --- upgradeable timer callback, don't want to keep calling the old function if the library is upgraded -HereBeDragons.UpdateCurrentPosition = UpdateCurrentPosition -local function UpdateTimerCallback() - -- signal that the timer ran - HereBeDragons.updateTimerActive = nil - - -- run update now - HereBeDragons.UpdateCurrentPosition() -end - -function StartUpdateTimer() - if not HereBeDragons.updateTimerActive then - -- prevent running multiple timers - HereBeDragons.updateTimerActive = true - - -- and queue an update - C_Timer.After(1, UpdateTimerCallback) - end -end - -local function OnEvent(frame, event, ...) - UpdateCurrentPosition() -end - -HereBeDragons.eventFrame:SetScript("OnEvent", OnEvent) -HereBeDragons.eventFrame:UnregisterAllEvents() -HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA") -HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED") -HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED_INDOORS") -HereBeDragons.eventFrame:RegisterEvent("NEW_WMO_CHUNK") -HereBeDragons.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD") - --- if we're loading after entering the world (ie. on demand), update position now -if IsLoggedIn() then - UpdateCurrentPosition() -end - ---- Return the localized zone name for a given uiMapID --- @param uiMapID uiMapID of the zone -function HereBeDragons:GetLocalizedMap(uiMapID) - return mapData[uiMapID] and mapData[uiMapID].name or nil -end - ---- Get the size of the zone --- @param uiMapID uiMapID of the zone --- @return width, height of the zone, in yards -function HereBeDragons:GetZoneSize(uiMapID) - local data = mapData[uiMapID] - if not data then return 0, 0 end - - return data[1], data[2] -end - ---- Get a list of all map IDs --- @return array-style table with all known/valid map IDs -function HereBeDragons:GetAllMapIDs() - local t = {} - for id in pairs(mapData) do - table.insert(t, id) - end - return t -end - ---- Convert local/point coordinates to world coordinates in yards --- @param x X position in 0-1 point coordinates --- @param y Y position in 0-1 point coordinates --- @param zone uiMapID of the zone -function HereBeDragons:GetWorldCoordinatesFromZone(x, y, zone) - local data = mapData[zone] - if not data or data[1] == 0 or data[2] == 0 then return nil, nil, nil end - if not x or not y then return nil, nil, nil end - - local width, height, left, top = data[1], data[2], data[3], data[4] - x, y = left - width * x, top - height * y - - return x, y, data.instance -end - ---- Convert local/point coordinates to world coordinates in yards. The coordinates have to come from the Azeroth World Map --- @param x X position in 0-1 point coordinates --- @param y Y position in 0-1 point coordinates --- @param instance Instance to use for the world coordinates -function HereBeDragons:GetWorldCoordinatesFromAzerothWorldMap(x, y, instance) - local data = worldMapData[instance] - if not data or data[1] == 0 or data[2] == 0 then return nil, nil, nil end - if not x or not y then return nil, nil, nil end - - local width, height, left, top = data[1], data[2], data[3], data[4] - x, y = left - width * x, top - height * y - - return x, y, instance -end - - ---- Convert world coordinates to local/point zone coordinates --- @param x Global X position --- @param y Global Y position --- @param zone uiMapID of the zone --- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned -function HereBeDragons:GetZoneCoordinatesFromWorld(x, y, zone, allowOutOfBounds) - local data = mapData[zone] - if not data or data[1] == 0 or data[2] == 0 then return nil, nil end - if not x or not y then return nil, nil end - - local width, height, left, top = data[1], data[2], data[3], data[4] - x, y = (left - x) / width, (top - y) / height - - -- verify the coordinates fall into the zone - if not allowOutOfBounds and (x < 0 or x > 1 or y < 0 or y > 1) then return nil, nil end - - return x, y -end - ---- Convert world coordinates to local/point zone coordinates on the azeroth world map --- @param x Global X position --- @param y Global Y position --- @param instance Instance to translate coordinates from --- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned -function HereBeDragons:GetAzerothWorldMapCoordinatesFromWorld(x, y, instance, allowOutOfBounds) - local data = worldMapData[instance] - if not data or data[1] == 0 or data[2] == 0 then return nil, nil end - if not x or not y then return nil, nil end - - local width, height, left, top = data[1], data[2], data[3], data[4] - x, y = (left - x) / width, (top - y) / height - - -- verify the coordinates fall into the zone - if not allowOutOfBounds and (x < 0 or x > 1 or y < 0 or y > 1) then return nil, nil end - - return x, y -end - --- Helper function to handle world map coordinate translation -local function TranslateAzerothWorldMapCoordinates(self, x, y, oZone, dZone, allowOutOfBounds) - if (oZone ~= WORLD_MAP_ID and not mapData[oZone]) or (dZone ~= WORLD_MAP_ID and not mapData[dZone]) then return nil, nil end - -- determine the instance we're working with - local instance = (oZone == WORLD_MAP_ID) and mapData[dZone].instance or mapData[oZone].instance - if not worldMapData[instance] then return nil, nil end - - if oZone == WORLD_MAP_ID then - x, y = self:GetWorldCoordinatesFromAzerothWorldMap(x, y, instance) - return self:GetZoneCoordinatesFromWorld(x, y, dZone, allowOutOfBounds) - else - x, y = self:GetWorldCoordinatesFromZone(x, y, oZone) - return self:GetAzerothWorldMapCoordinatesFromWorld(x, y, instance, allowOutOfBounds) - end -end - ---- Translate zone coordinates from one zone to another --- @param x X position in 0-1 point coordinates, relative to the origin zone --- @param y Y position in 0-1 point coordinates, relative to the origin zone --- @param oZone Origin Zone, uiMapID --- @param dZone Destination Zone, uiMapID --- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned -function HereBeDragons:TranslateZoneCoordinates(x, y, oZone, dZone, allowOutOfBounds) - if oZone == dZone then return x, y end - - if oZone == WORLD_MAP_ID or dZone == WORLD_MAP_ID then - return TranslateAzerothWorldMapCoordinates(self, x, y, oZone, dZone, allowOutOfBounds) - end - - local xCoord, yCoord, instance = self:GetWorldCoordinatesFromZone(x, y, oZone) - if not xCoord then return nil, nil end - - local data = mapData[dZone] - if not data or data.instance ~= instance then return nil, nil end - - return self:GetZoneCoordinatesFromWorld(xCoord, yCoord, dZone, allowOutOfBounds) -end - ---- Return the distance from an origin position to a destination position in the same instance/continent. --- @param instanceID instance ID --- @param oX origin X --- @param oY origin Y --- @param dX destination X --- @param dY destination Y --- @return distance, deltaX, deltaY -function HereBeDragons:GetWorldDistance(instanceID, oX, oY, dX, dY) - if not oX or not oY or not dX or not dY then return nil, nil, nil end - local deltaX, deltaY = dX - oX, dY - oY - return (deltaX * deltaX + deltaY * deltaY)^0.5, deltaX, deltaY -end - ---- Return the distance between two points on the same continent --- @param oZone origin zone uiMapID --- @param oX origin X, in local zone/point coordinates --- @param oY origin Y, in local zone/point coordinates --- @param dZone destination zone uiMapID --- @param dX destination X, in local zone/point coordinates --- @param dY destination Y, in local zone/point coordinates --- @return distance, deltaX, deltaY in yards -function HereBeDragons:GetZoneDistance(oZone, oX, oY, dZone, dX, dY) - local oInstance, dInstance - oX, oY, oInstance = self:GetWorldCoordinatesFromZone(oX, oY, oZone) - if not oX then return nil, nil, nil end - - -- translate dX, dY to the origin zone - dX, dY, dInstance = self:GetWorldCoordinatesFromZone(dX, dY, dZone) - if not dX then return nil, nil, nil end - - if oInstance ~= dInstance then return nil, nil, nil end - - return self:GetWorldDistance(oInstance, oX, oY, dX, dY) -end - ---- Return the angle and distance from an origin position to a destination position in the same instance/continent. --- @param instanceID instance ID --- @param oX origin X --- @param oY origin Y --- @param dX destination X --- @param dY destination Y --- @return angle, distance where angle is in radians and distance in yards -function HereBeDragons:GetWorldVector(instanceID, oX, oY, dX, dY) - local distance, deltaX, deltaY = self:GetWorldDistance(instanceID, oX, oY, dX, dY) - if not distance then return nil, nil end - - -- calculate the angle from deltaY and deltaX - local angle = atan2(-deltaX, deltaY) - - -- normalize the angle - if angle > 0 then - angle = PI2 - angle - else - angle = -angle - end - - return angle, distance -end - ---- Get the current world position of the specified unit --- The position is transformed to the current continent, if applicable --- NOTE: The same restrictions as for the UnitPosition() API apply, --- which means a very limited set of unit ids will actually work. --- @param unitId Unit Id --- @return x, y, instanceID -function HereBeDragons:GetUnitWorldPosition(unitId) - -- get the current position - local y, x, _z, instanceID = UnitPosition(unitId) - if not x or not y then return nil, nil, instanceIDOverrides[instanceID] or instanceID end - - -- return transformed coordinates - return applyCoordinateTransforms(x, y, instanceID) -end - ---- Get the current world position of the player --- The position is transformed to the current continent, if applicable --- @return x, y, instanceID -function HereBeDragons:GetPlayerWorldPosition() - -- get the current position - local y, x, _z, instanceID = UnitPosition("player") - if not x or not y then return nil, nil, instanceIDOverrides[instanceID] or instanceID end - - -- return transformed coordinates - return applyCoordinateTransforms(x, y, instanceID) -end - ---- Get the current zone and level of the player --- The returned mapFile can represent a micro dungeon, if the player currently is inside one. --- @return uiMapID, mapType -function HereBeDragons:GetPlayerZone() - return currentPlayerUIMapID, currentPlayerUIMapType -end - ---- Get the current position of the player on a zone level --- The returned values are local point coordinates, 0-1. The mapFile can represent a micro dungeon. --- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned --- @return x, y, uiMapID, mapType -function HereBeDragons:GetPlayerZonePosition(allowOutOfBounds) - if not currentPlayerUIMapID then return nil, nil, nil, nil end - local x, y, _instanceID = self:GetPlayerWorldPosition() - if not x or not y then return nil, nil, nil, nil end - - x, y = self:GetZoneCoordinatesFromWorld(x, y, currentPlayerUIMapID, allowOutOfBounds) - if x and y then - return x, y, currentPlayerUIMapID, currentPlayerUIMapType - end - return nil, nil, nil, nil -end +-- HereBeDragons is a data API for the World of Warcraft mapping system + +local MAJOR, MINOR = "HereBeDragons-2.0", 16 +assert(LibStub, MAJOR .. " requires LibStub") + +local HereBeDragons, oldversion = LibStub:NewLibrary(MAJOR, MINOR) +if not HereBeDragons then return end + +local CBH = LibStub("CallbackHandler-1.0") + +HereBeDragons.eventFrame = HereBeDragons.eventFrame or CreateFrame("Frame") + +HereBeDragons.mapData = HereBeDragons.mapData or {} +HereBeDragons.worldMapData = HereBeDragons.worldMapData or {} +HereBeDragons.transforms = HereBeDragons.transforms or {} +HereBeDragons.callbacks = HereBeDragons.callbacks or CBH:New(HereBeDragons, nil, nil, false) + +local WoWClassic = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC) +local WoWBC = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC) + +-- Data Constants +local COSMIC_MAP_ID = 946 +local WORLD_MAP_ID = 947 + +-- Lua upvalues +local PI2 = math.pi * 2 +local atan2 = math.atan2 +local pairs, ipairs = pairs, ipairs + +-- WoW API upvalues +local UnitPosition = UnitPosition +local C_Map = C_Map + +-- data table upvalues +local mapData = HereBeDragons.mapData -- table { width, height, left, top, .instance, .name, .mapType } +local worldMapData = HereBeDragons.worldMapData -- table { width, height, left, top } +local transforms = HereBeDragons.transforms + +local currentPlayerUIMapID, currentPlayerUIMapType + +-- Override instance ids for phased content +local instanceIDOverrides = { + -- Draenor + [1152] = 1116, -- Horde Garrison 1 + [1330] = 1116, -- Horde Garrison 2 + [1153] = 1116, -- Horde Garrison 3 + [1154] = 1116, -- Horde Garrison 4 (unused) + [1158] = 1116, -- Alliance Garrison 1 + [1331] = 1116, -- Alliance Garrison 2 + [1159] = 1116, -- Alliance Garrison 3 + [1160] = 1116, -- Alliance Garrison 4 (unused) + [1191] = 1116, -- Ashran PvP Zone + [1203] = 1116, -- Frostfire Finale Scenario + [1207] = 1116, -- Talador Finale Scenario + [1277] = 1116, -- Defense of Karabor Scenario (SMV) + [1402] = 1116, -- Gorgrond Finale Scenario + [1464] = 1116, -- Tanaan + [1465] = 1116, -- Tanaan + -- Legion + [1478] = 1220, -- Temple of Elune Scenario (Val'Sharah) + [1495] = 1220, -- Protection Paladin Artifact Scenario (Stormheim) + [1498] = 1220, -- Havoc Demon Hunter Artifact Scenario (Suramar) + [1502] = 1220, -- Dalaran Underbelly + [1533] = 0, -- Karazhan Artifact Scenario + [1539] = 0, -- Arm Warrior Artifact Tirisfal Scenario + [1612] = 1220, -- Feral Druid Artifact Scenario (Suramar) + [1626] = 1220, -- Suramar Withered Scenario + [1662] = 1220, -- Suramar Invasion Scenario + -- BfA + [2213] = 0, -- Horrific Vision of Stormwind + [2241] = 1, -- Uldum N'zoth assault + [2274] = 1, -- Uldum N'zoth Minor Vision + [2275] = 870, -- Vale of Eternal Blossoms N'zoth Minor Vision +} + +local dynamicInstanceIDOverrides = {} +instanceIDOverrides = setmetatable(instanceIDOverrides, { __index = dynamicInstanceIDOverrides }) + +local function overrideInstance(instance) return instanceIDOverrides[instance] or instance end + +-- debug only +HereBeDragons.___DIIDO = dynamicInstanceIDOverrides + +-- gather map info, but only if this isn't an upgrade (or the upgrade version forces a re-map) +if not oldversion or oldversion < 16 then + -- wipe old data, if required, otherwise the upgrade path isn't triggered + if oldversion then + wipe(mapData) + wipe(worldMapData) + wipe(transforms) + end + + -- map transform data extracted from UIMapAssignment.db2 (see HereBeDragons-Scripts on GitHub) + -- format: instanceID, newInstanceID, minY, maxY, minX, maxX, offsetY, offsetX + local transformData + if WoWBC then + transformData = { + { 530, 0, 4800, 16000, -10133.3, -2666.67, -2400, 2662.8 }, + { 530, 1, -6933.33, 533.33, -16000, -8000, 10339.7, 17600 }, + } + else + transformData = { + { 530, 1, -6933.33, 533.33, -16000, -8000, 9916, 17600 }, + { 530, 0, 4800, 16000, -10133.3, -2666.67, -2400, 2400 }, + { 732, 0, -3200, 533.3, -533.3, 2666.7, -611.8, 3904.3 }, + { 1064, 870, 5391, 8148, 3518, 7655, -2134.2, -2286.6 }, + { 1208, 1116, -2666, -2133, -2133, -1600, 10210.7, 2411.4 }, + { 1460, 1220, -1066.7, 2133.3, 0, 3200, -2333.9, 966.7 }, + { 1599, 1, 4800, 5866.7, -4266.7, -3200, -490.6, -0.4 }, + { 1609, 571, 6400, 8533.3, -1600, 533.3, 512.8, 545.3 }, + } + end + + local function processTransforms() + for _, transform in pairs(transformData) do + local instanceID, newInstanceID, minY, maxY, minX, maxX, offsetY, offsetX = unpack(transform) + if not transforms[instanceID] then + transforms[instanceID] = {} + end + table.insert(transforms[instanceID], { newInstanceID = newInstanceID, minY = minY, maxY = maxY, minX = minX, maxX = maxX, offsetY = offsetY, offsetX = offsetX }) + end + end + + local function applyMapTransforms(instanceID, left, right, top, bottom) + if transforms[instanceID] then + for _, data in ipairs(transforms[instanceID]) do + if left <= data.maxX and right >= data.minX and top <= data.maxY and bottom >= data.minY then + instanceID = data.newInstanceID + left = left + data.offsetX + right = right + data.offsetX + top = top + data.offsetY + bottom = bottom + data.offsetY + break + end + end + end + return instanceID, left, right, top, bottom + end + + local vector00, vector05 = CreateVector2D(0, 0), CreateVector2D(0.5, 0.5) + -- gather the data of one map (by uiMapID) + local function processMap(id, data, parent) + if not id or not data or mapData[id] then return end + + if data.parentMapID and data.parentMapID ~= 0 then + parent = data.parentMapID + elseif not parent then + parent = 0 + end + + -- get two positions from the map, we use 0/0 and 0.5/0.5 to avoid issues on some maps where 1/1 is translated inaccurately + local instance, topLeft = C_Map.GetWorldPosFromMapPos(id, vector00) + local _, bottomRight = C_Map.GetWorldPosFromMapPos(id, vector05) + if topLeft and bottomRight then + local top, left = topLeft:GetXY() + local bottom, right = bottomRight:GetXY() + bottom = top + (bottom - top) * 2 + right = left + (right - left) * 2 + + instance, left, right, top, bottom = applyMapTransforms(instance, left, right, top, bottom) + mapData[id] = {left - right, top - bottom, left, top, instance = instance, name = data.name, mapType = data.mapType, parent = parent } + else + mapData[id] = {0, 0, 0, 0, instance = instance or -1, name = data.name, mapType = data.mapType, parent = parent } + end + end + + local function processMapChildrenRecursive(parent) + local children = C_Map.GetMapChildrenInfo(parent) + if children and #children > 0 then + for i = 1, #children do + local id = children[i].mapID + if id and not mapData[id] then + processMap(id, children[i], parent) + processMapChildrenRecursive(id) + + -- process sibling maps (in the same group) + -- in some cases these are not discovered by GetMapChildrenInfo above + local groupID = C_Map.GetMapGroupID(id) + if groupID then + local groupMembers = C_Map.GetMapGroupMembersInfo(groupID) + if groupMembers then + for k = 1, #groupMembers do + local memberId = groupMembers[k].mapID + if memberId and not mapData[memberId] then + processMap(memberId, C_Map.GetMapInfo(memberId), parent) + processMapChildrenRecursive(memberId) + end + end + end + end + end + end + end + end + + local function fixupZones() + local cosmic = C_Map.GetMapInfo(COSMIC_MAP_ID) + if cosmic then + mapData[COSMIC_MAP_ID] = {0, 0, 0, 0} + mapData[COSMIC_MAP_ID].instance = -1 + mapData[COSMIC_MAP_ID].name = cosmic.name + mapData[COSMIC_MAP_ID].mapType = cosmic.mapType + end + + -- data for the azeroth world map + if WoWClassic then + worldMapData[0] = { 44688.53, 29795.11, 32601.04, 9894.93 } + worldMapData[1] = { 44878.66, 29916.10, 8723.96, 14824.53 } + elseif WoWBC then + worldMapData[0] = { 44688.53, 29791.24, 32681.47, 11479.44 } + worldMapData[1] = { 44878.66, 29916.10, 8723.96, 14824.53 } + else + worldMapData[0] = { 76153.14, 50748.62, 65008.24, 23827.51 } + worldMapData[1] = { 77803.77, 51854.98, 13157.6, 28030.61 } + worldMapData[571] = { 71773.64, 50054.05, 36205.94, 12366.81 } + worldMapData[870] = { 67710.54, 45118.08, 33565.89, 38020.67 } + worldMapData[1220] = { 82758.64, 55151.28, 52943.46, 24484.72 } + worldMapData[1642] = { 77933.3, 51988.91, 44262.36, 32835.1 } + worldMapData[1643] = { 76060.47, 50696.96, 55384.8, 25774.35 } + end + end + + local function gatherMapData() + processTransforms() + + -- find all maps in well known structures + if WoWClassic then + processMap(WORLD_MAP_ID) + processMapChildrenRecursive(WORLD_MAP_ID) + else + processMapChildrenRecursive(COSMIC_MAP_ID) + end + + fixupZones() + + -- try to fill in holes in the map list + for i = 1, 2000 do + if not mapData[i] then + local mapInfo = C_Map.GetMapInfo(i) + if mapInfo and mapInfo.name then + processMap(i, mapInfo, nil) + end + end + end + end + + gatherMapData() +end + +-- Transform a set of coordinates based on the defined map transformations +local function applyCoordinateTransforms(x, y, instanceID) + if transforms[instanceID] then + for _, transformData in ipairs(transforms[instanceID]) do + if transformData.minX <= x and transformData.maxX >= x and transformData.minY <= y and transformData.maxY >= y then + instanceID = transformData.newInstanceID + x = x + transformData.offsetX + y = y + transformData.offsetY + break + end + end + end + return x, y, overrideInstance(instanceID) +end + +local StartUpdateTimer +local function UpdateCurrentPosition(instanceCheck) + -- retrieve current zone + local uiMapID = C_Map.GetBestMapForUnit("player") + + -- try to override the instance if possible + if instanceCheck then + local _x, _y, instance = HereBeDragons:GetPlayerWorldPosition() + if instance and instance ~= -1 and mapData[uiMapID] and mapData[uiMapID].instance ~= instance and uiMapID ~= -1 and not instanceIDOverrides[instance] and not instanceIDOverrides[mapData[uiMapID].instance] then + dynamicInstanceIDOverrides[instance] = mapData[uiMapID].instance + end + end + + if uiMapID ~= currentPlayerUIMapID then + -- update location upvalues + currentPlayerUIMapID, currentPlayerUIMapType = uiMapID, mapData[uiMapID] and mapData[uiMapID].mapType or 0 + + -- signal callback + HereBeDragons.callbacks:Fire("PlayerZoneChanged", currentPlayerUIMapID, currentPlayerUIMapType) + end + + -- start a timer to update in micro dungeons since multi-level micro dungeons do not reliably fire events + if currentPlayerUIMapType == Enum.UIMapType.Micro then + StartUpdateTimer() + end +end + +-- upgradeable timer callback, don't want to keep calling the old function if the library is upgraded +HereBeDragons.UpdateCurrentPosition = UpdateCurrentPosition +local function UpdateTimerCallback() + -- signal that the timer ran + HereBeDragons.updateTimerActive = nil + + -- run update now + HereBeDragons.UpdateCurrentPosition() +end + +function StartUpdateTimer() + if not HereBeDragons.updateTimerActive then + -- prevent running multiple timers + HereBeDragons.updateTimerActive = true + + -- and queue an update + C_Timer.After(1, UpdateTimerCallback) + end +end + +local function OnEvent(frame, event, ...) + UpdateCurrentPosition(true) +end + +HereBeDragons.eventFrame:SetScript("OnEvent", OnEvent) +HereBeDragons.eventFrame:UnregisterAllEvents() +HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA") +HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED") +HereBeDragons.eventFrame:RegisterEvent("ZONE_CHANGED_INDOORS") +HereBeDragons.eventFrame:RegisterEvent("NEW_WMO_CHUNK") +HereBeDragons.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + +-- if we're loading after entering the world (ie. on demand), update position now +if IsLoggedIn() then + UpdateCurrentPosition(true) +end + +--- Return the localized zone name for a given uiMapID +-- @param uiMapID uiMapID of the zone +function HereBeDragons:GetLocalizedMap(uiMapID) + return mapData[uiMapID] and mapData[uiMapID].name or nil +end + +--- Get the size of the zone +-- @param uiMapID uiMapID of the zone +-- @return width, height of the zone, in yards +function HereBeDragons:GetZoneSize(uiMapID) + local data = mapData[uiMapID] + if not data then return 0, 0 end + + return data[1], data[2] +end + +--- Get a list of all map IDs +-- @return array-style table with all known/valid map IDs +function HereBeDragons:GetAllMapIDs() + local t = {} + for id in pairs(mapData) do + table.insert(t, id) + end + return t +end + +--- Convert local/point coordinates to world coordinates in yards +-- @param x X position in 0-1 point coordinates +-- @param y Y position in 0-1 point coordinates +-- @param zone uiMapID of the zone +function HereBeDragons:GetWorldCoordinatesFromZone(x, y, zone) + local data = mapData[zone] + if not data or data[1] == 0 or data[2] == 0 then return nil, nil, nil end + if not x or not y then return nil, nil, nil end + + local width, height, left, top = data[1], data[2], data[3], data[4] + x, y = left - width * x, top - height * y + + return x, y, overrideInstance(data.instance) +end + +--- Convert local/point coordinates to world coordinates in yards. The coordinates have to come from the Azeroth World Map +-- @param x X position in 0-1 point coordinates +-- @param y Y position in 0-1 point coordinates +-- @param instance Instance to use for the world coordinates +function HereBeDragons:GetWorldCoordinatesFromAzerothWorldMap(x, y, instance) + local data = worldMapData[instance] + if not data or data[1] == 0 or data[2] == 0 then return nil, nil, nil end + if not x or not y then return nil, nil, nil end + + local width, height, left, top = data[1], data[2], data[3], data[4] + x, y = left - width * x, top - height * y + + return x, y, instance +end + + +--- Convert world coordinates to local/point zone coordinates +-- @param x Global X position +-- @param y Global Y position +-- @param zone uiMapID of the zone +-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned +function HereBeDragons:GetZoneCoordinatesFromWorld(x, y, zone, allowOutOfBounds) + local data = mapData[zone] + if not data or data[1] == 0 or data[2] == 0 then return nil, nil end + if not x or not y then return nil, nil end + + local width, height, left, top = data[1], data[2], data[3], data[4] + x, y = (left - x) / width, (top - y) / height + + -- verify the coordinates fall into the zone + if not allowOutOfBounds and (x < 0 or x > 1 or y < 0 or y > 1) then return nil, nil end + + return x, y +end + +--- Convert world coordinates to local/point zone coordinates on the azeroth world map +-- @param x Global X position +-- @param y Global Y position +-- @param instance Instance to translate coordinates from +-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned +function HereBeDragons:GetAzerothWorldMapCoordinatesFromWorld(x, y, instance, allowOutOfBounds) + local data = worldMapData[instance] + if not data or data[1] == 0 or data[2] == 0 then return nil, nil end + if not x or not y then return nil, nil end + + local width, height, left, top = data[1], data[2], data[3], data[4] + x, y = (left - x) / width, (top - y) / height + + -- verify the coordinates fall into the zone + if not allowOutOfBounds and (x < 0 or x > 1 or y < 0 or y > 1) then return nil, nil end + + return x, y +end + +-- Helper function to handle world map coordinate translation +local function TranslateAzerothWorldMapCoordinates(self, x, y, oZone, dZone, allowOutOfBounds) + if (oZone ~= WORLD_MAP_ID and not mapData[oZone]) or (dZone ~= WORLD_MAP_ID and not mapData[dZone]) then return nil, nil end + -- determine the instance we're working with + local instance = overrideInstance((oZone == WORLD_MAP_ID) and mapData[dZone].instance or mapData[oZone].instance) + if not worldMapData[instance] then return nil, nil end + + if oZone == WORLD_MAP_ID then + x, y = self:GetWorldCoordinatesFromAzerothWorldMap(x, y, instance) + return self:GetZoneCoordinatesFromWorld(x, y, dZone, allowOutOfBounds) + else + x, y = self:GetWorldCoordinatesFromZone(x, y, oZone) + return self:GetAzerothWorldMapCoordinatesFromWorld(x, y, instance, allowOutOfBounds) + end +end + +--- Translate zone coordinates from one zone to another +-- @param x X position in 0-1 point coordinates, relative to the origin zone +-- @param y Y position in 0-1 point coordinates, relative to the origin zone +-- @param oZone Origin Zone, uiMapID +-- @param dZone Destination Zone, uiMapID +-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned +function HereBeDragons:TranslateZoneCoordinates(x, y, oZone, dZone, allowOutOfBounds) + if oZone == dZone then return x, y end + + if oZone == WORLD_MAP_ID or dZone == WORLD_MAP_ID then + return TranslateAzerothWorldMapCoordinates(self, x, y, oZone, dZone, allowOutOfBounds) + end + + local xCoord, yCoord, instance = self:GetWorldCoordinatesFromZone(x, y, oZone) + if not xCoord then return nil, nil end + + local data = mapData[dZone] + if not data or overrideInstance(data.instance) ~= instance then return nil, nil end + + return self:GetZoneCoordinatesFromWorld(xCoord, yCoord, dZone, allowOutOfBounds) +end + +--- Return the distance from an origin position to a destination position in the same instance/continent. +-- @param instanceID instance ID +-- @param oX origin X +-- @param oY origin Y +-- @param dX destination X +-- @param dY destination Y +-- @return distance, deltaX, deltaY +function HereBeDragons:GetWorldDistance(instanceID, oX, oY, dX, dY) + if not oX or not oY or not dX or not dY then return nil, nil, nil end + local deltaX, deltaY = dX - oX, dY - oY + return (deltaX * deltaX + deltaY * deltaY)^0.5, deltaX, deltaY +end + +--- Return the distance between two points on the same continent +-- @param oZone origin zone uiMapID +-- @param oX origin X, in local zone/point coordinates +-- @param oY origin Y, in local zone/point coordinates +-- @param dZone destination zone uiMapID +-- @param dX destination X, in local zone/point coordinates +-- @param dY destination Y, in local zone/point coordinates +-- @return distance, deltaX, deltaY in yards +function HereBeDragons:GetZoneDistance(oZone, oX, oY, dZone, dX, dY) + local oInstance, dInstance + oX, oY, oInstance = self:GetWorldCoordinatesFromZone(oX, oY, oZone) + if not oX then return nil, nil, nil end + + -- translate dX, dY to the origin zone + dX, dY, dInstance = self:GetWorldCoordinatesFromZone(dX, dY, dZone) + if not dX then return nil, nil, nil end + + if oInstance ~= dInstance then return nil, nil, nil end + + return self:GetWorldDistance(oInstance, oX, oY, dX, dY) +end + +--- Return the angle and distance from an origin position to a destination position in the same instance/continent. +-- @param instanceID instance ID +-- @param oX origin X +-- @param oY origin Y +-- @param dX destination X +-- @param dY destination Y +-- @return angle, distance where angle is in radians and distance in yards +function HereBeDragons:GetWorldVector(instanceID, oX, oY, dX, dY) + local distance, deltaX, deltaY = self:GetWorldDistance(instanceID, oX, oY, dX, dY) + if not distance then return nil, nil end + + -- calculate the angle from deltaY and deltaX + local angle = atan2(-deltaX, deltaY) + + -- normalize the angle + if angle > 0 then + angle = PI2 - angle + else + angle = -angle + end + + return angle, distance +end + +--- Get the current world position of the specified unit +-- The position is transformed to the current continent, if applicable +-- NOTE: The same restrictions as for the UnitPosition() API apply, +-- which means a very limited set of unit ids will actually work. +-- @param unitId Unit Id +-- @return x, y, instanceID +function HereBeDragons:GetUnitWorldPosition(unitId) + -- get the current position + local y, x, _z, instanceID = UnitPosition(unitId) + if not x or not y then return nil, nil, overrideInstance(instanceID) end + + -- return transformed coordinates + return applyCoordinateTransforms(x, y, instanceID) +end + +--- Get the current world position of the player +-- The position is transformed to the current continent, if applicable +-- @return x, y, instanceID +function HereBeDragons:GetPlayerWorldPosition() + -- get the current position + local y, x, _z, instanceID = UnitPosition("player") + if not x or not y then return nil, nil, overrideInstance(instanceID) end + + -- return transformed coordinates + return applyCoordinateTransforms(x, y, instanceID) +end + +--- Get the current zone and level of the player +-- The returned mapFile can represent a micro dungeon, if the player currently is inside one. +-- @return uiMapID, mapType +function HereBeDragons:GetPlayerZone() + return currentPlayerUIMapID, currentPlayerUIMapType +end + +--- Get the current position of the player on a zone level +-- The returned values are local point coordinates, 0-1. The mapFile can represent a micro dungeon. +-- @param allowOutOfBounds Allow coordinates to go beyond the current map (ie. outside of the 0-1 range), otherwise nil will be returned +-- @return x, y, uiMapID, mapType +function HereBeDragons:GetPlayerZonePosition(allowOutOfBounds) + if not currentPlayerUIMapID then return nil, nil, nil, nil end + local x, y, _instanceID = self:GetPlayerWorldPosition() + if not x or not y then return nil, nil, nil, nil end + + x, y = self:GetZoneCoordinatesFromWorld(x, y, currentPlayerUIMapID, allowOutOfBounds) + if x and y then + return x, y, currentPlayerUIMapID, currentPlayerUIMapType + end + return nil, nil, nil, nil +end diff --git a/Libraries/HereBeDragons-2.0/HereBeDragons-Pins-2.0.lua b/Libraries/HereBeDragons-2.0/HereBeDragons-Pins-2.0.lua index 6470ec9..5d74784 100644 --- a/Libraries/HereBeDragons-2.0/HereBeDragons-Pins-2.0.lua +++ b/Libraries/HereBeDragons-2.0/HereBeDragons-Pins-2.0.lua @@ -1,752 +1,770 @@ --- HereBeDragons-Pins is a library to show pins/icons on the world map and minimap - -local MAJOR, MINOR = "HereBeDragons-Pins-2.0", 6 -assert(LibStub, MAJOR .. " requires LibStub") - -local pins, _oldversion = LibStub:NewLibrary(MAJOR, MINOR) -if not pins then return end - -local HBD = LibStub("HereBeDragons-2.0") - -pins.updateFrame = pins.updateFrame or CreateFrame("Frame") - --- storage for minimap pins -pins.minimapPins = pins.minimapPins or {} -pins.activeMinimapPins = pins.activeMinimapPins or {} -pins.minimapPinRegistry = pins.minimapPinRegistry or {} - --- and worldmap pins -pins.worldmapPins = pins.worldmapPins or {} -pins.worldmapPinRegistry = pins.worldmapPinRegistry or {} -pins.worldmapPinsPool = pins.worldmapPinsPool or CreateFramePool("FRAME") -pins.worldmapProvider = pins.worldmapProvider or CreateFromMixins(MapCanvasDataProviderMixin) -pins.worldmapProviderPin = pins.worldmapProviderPin or CreateFromMixins(MapCanvasPinMixin) - --- store a reference to the active minimap object -pins.Minimap = pins.Minimap or Minimap - --- Data Constants -local WORLD_MAP_ID = 947 - --- upvalue lua api -local cos, sin, max = math.cos, math.sin, math.max -local type, pairs = type, pairs - --- upvalue wow api -local GetPlayerFacing = GetPlayerFacing - --- upvalue data tables -local minimapPins = pins.minimapPins -local activeMinimapPins = pins.activeMinimapPins -local minimapPinRegistry = pins.minimapPinRegistry - -local worldmapPins = pins.worldmapPins -local worldmapPinRegistry = pins.worldmapPinRegistry -local worldmapPinsPool = pins.worldmapPinsPool -local worldmapProvider = pins.worldmapProvider -local worldmapProviderPin = pins.worldmapProviderPin - -local minimap_size = { - indoor = { - [0] = 300, -- scale - [1] = 240, -- 1.25 - [2] = 180, -- 5/3 - [3] = 120, -- 2.5 - [4] = 80, -- 3.75 - [5] = 50, -- 6 - }, - outdoor = { - [0] = 466 + 2/3, -- scale - [1] = 400, -- 7/6 - [2] = 333 + 1/3, -- 1.4 - [3] = 266 + 2/6, -- 1.75 - [4] = 200, -- 7/3 - [5] = 133 + 1/3, -- 3.5 - }, -} - -local minimap_shapes = { - -- { upper-left, lower-left, upper-right, lower-right } - ["SQUARE"] = { false, false, false, false }, - ["CORNER-TOPLEFT"] = { true, false, false, false }, - ["CORNER-TOPRIGHT"] = { false, false, true, false }, - ["CORNER-BOTTOMLEFT"] = { false, true, false, false }, - ["CORNER-BOTTOMRIGHT"] = { false, false, false, true }, - ["SIDE-LEFT"] = { true, true, false, false }, - ["SIDE-RIGHT"] = { false, false, true, true }, - ["SIDE-TOP"] = { true, false, true, false }, - ["SIDE-BOTTOM"] = { false, true, false, true }, - ["TRICORNER-TOPLEFT"] = { true, true, true, false }, - ["TRICORNER-TOPRIGHT"] = { true, false, true, true }, - ["TRICORNER-BOTTOMLEFT"] = { true, true, false, true }, - ["TRICORNER-BOTTOMRIGHT"] = { false, true, true, true }, -} - -local tableCache = setmetatable({}, {__mode='k'}) - -local function newCachedTable() - local t = next(tableCache) - if t then - tableCache[t] = nil - else - t = {} - end - return t -end - -local function recycle(t) - tableCache[t] = true -end - -------------------------------------------------------------------------------------------- --- Minimap pin position logic - --- minimap rotation -local rotateMinimap = GetCVar("rotateMinimap") == "1" - --- is the minimap indoors or outdoors -local indoors = GetCVar("minimapZoom")+0 == pins.Minimap:GetZoom() and "outdoor" or "indoor" - -local minimapPinCount, queueFullUpdate = 0, false -local minimapScale, minimapShape, mapRadius, minimapWidth, minimapHeight, mapSin, mapCos -local lastZoom, lastFacing, lastXY, lastYY - -local function drawMinimapPin(pin, data) - local xDist, yDist = lastXY - data.x, lastYY - data.y - - -- handle rotation - if rotateMinimap then - local dx, dy = xDist, yDist - xDist = dx*mapCos - dy*mapSin - yDist = dx*mapSin + dy*mapCos - end - - -- adapt delta position to the map radius - local diffX = xDist / mapRadius - local diffY = yDist / mapRadius - - -- different minimap shapes - local isRound = true - if minimapShape and not (xDist == 0 or yDist == 0) then - isRound = (xDist < 0) and 1 or 3 - if yDist < 0 then - isRound = minimapShape[isRound] - else - isRound = minimapShape[isRound + 1] - end - end - - -- calculate distance from the center of the map - local dist - if isRound then - dist = (diffX*diffX + diffY*diffY) / 0.9^2 - else - dist = max(diffX*diffX, diffY*diffY) / 0.9^2 - end - - -- if distance > 1, then adapt node position to slide on the border - if dist > 1 and data.floatOnEdge then - dist = dist^0.5 - diffX = diffX/dist - diffY = diffY/dist - end - - if dist <= 1 or data.floatOnEdge then - pin:Show() - pin:ClearAllPoints() - pin:SetPoint("CENTER", pins.Minimap, "CENTER", diffX * minimapWidth, -diffY * minimapHeight) - data.onEdge = (dist > 1) - else - pin:Hide() - data.onEdge = nil - pin.keep = nil - end -end - -local function IsParentMap(originMapId, toCheckMapId) - local parentMapID = HBD.mapData[originMapId].parent - while parentMapID and HBD.mapData[parentMapID] do - local mapType = HBD.mapData[parentMapID].mapType - if mapType ~= Enum.UIMapType.Zone and mapType ~= Enum.UIMapType.Dungeon and mapType ~= Enum.UIMapType.Micro then - return false - end - if parentMapID == toCheckMapId then - return true - end - parentMapID = HBD.mapData[parentMapID].parent - end - return false -end - -local function UpdateMinimapPins(force) - -- get the current player position - local x, y, instanceID = HBD:GetPlayerWorldPosition() - local mapID = HBD:GetPlayerZone() - - -- get data from the API for calculations - local zoom = pins.Minimap:GetZoom() - local diffZoom = zoom ~= lastZoom - - -- for rotating minimap support - local facing - if rotateMinimap then - facing = GetPlayerFacing() - else - facing = lastFacing - end - - -- check for all values to be available (starting with 7.1.0, instances don't report coordinates) - if not x or not y or (rotateMinimap and not facing) then - minimapPinCount = 0 - for pin in pairs(activeMinimapPins) do - pin:Hide() - activeMinimapPins[pin] = nil - end - return - end - - local newScale = pins.Minimap:GetScale() - if minimapScale ~= newScale then - minimapScale = newScale - force = true - end - - if x ~= lastXY or y ~= lastYY or diffZoom or facing ~= lastFacing or force then - -- minimap information - minimapShape = GetMinimapShape and minimap_shapes[GetMinimapShape() or "ROUND"] - mapRadius = minimap_size[indoors][zoom] / 2 - minimapWidth = pins.Minimap:GetWidth() / 2 - minimapHeight = pins.Minimap:GetHeight() / 2 - - -- update upvalues for icon placement - lastZoom = zoom - lastFacing = facing - lastXY, lastYY = x, y - - if rotateMinimap then - mapSin = sin(facing) - mapCos = cos(facing) - end - - for pin, data in pairs(minimapPins) do - if data.instanceID == instanceID and (not data.uiMapID or data.uiMapID == mapID or (data.showInParentZone and IsParentMap(data.uiMapID, mapID))) then - activeMinimapPins[pin] = data - data.keep = true - -- draw the pin (this may reset data.keep if outside of the map) - drawMinimapPin(pin, data) - end - end - - minimapPinCount = 0 - for pin, data in pairs(activeMinimapPins) do - if not data.keep then - pin:Hide() - activeMinimapPins[pin] = nil - else - minimapPinCount = minimapPinCount + 1 - data.keep = nil - end - end - end -end - -local function UpdateMinimapIconPosition() - - -- get the current map zoom - local zoom = pins.Minimap:GetZoom() - local diffZoom = zoom ~= lastZoom - -- if the map zoom changed, run a full update sweep - if diffZoom then - UpdateMinimapPins() - return - end - - -- we have no active minimap pins, just return early - if minimapPinCount == 0 then return end - - local x, y = HBD:GetPlayerWorldPosition() - - -- for rotating minimap support - local facing - if rotateMinimap then - facing = GetPlayerFacing() - else - facing = lastFacing - end - - -- check for all values to be available (starting with 7.1.0, instances don't report coordinates) - if not x or not y or (rotateMinimap and not facing) then - UpdateMinimapPins() - return - end - - local refresh - local newScale = pins.Minimap:GetScale() - if minimapScale ~= newScale then - minimapScale = newScale - refresh = true - end - - if x ~= lastXY or y ~= lastYY or facing ~= lastFacing or refresh then - -- update radius of the map - mapRadius = minimap_size[indoors][zoom] / 2 - -- update upvalues for icon placement - lastXY, lastYY = x, y - lastFacing = facing - - if rotateMinimap then - mapSin = sin(facing) - mapCos = cos(facing) - end - - -- iterate all nodes and check if they are still in range of our minimap display - for pin, data in pairs(activeMinimapPins) do - -- update the position of the node - drawMinimapPin(pin, data) - end - end -end - -local function UpdateMinimapZoom() - local zoom = pins.Minimap:GetZoom() - if GetCVar("minimapZoom") == GetCVar("minimapInsideZoom") then - pins.Minimap:SetZoom(zoom < 2 and zoom + 1 or zoom - 1) - end - indoors = GetCVar("minimapZoom")+0 == pins.Minimap:GetZoom() and "outdoor" or "indoor" - pins.Minimap:SetZoom(zoom) -end - -------------------------------------------------------------------------------------------- --- WorldMap data provider - --- setup pin pool -worldmapPinsPool.parent = WorldMapFrame:GetCanvas() -worldmapPinsPool.creationFunc = function(framePool) - local frame = CreateFrame(framePool.frameType, nil, framePool.parent) - frame:SetSize(1, 1) - return Mixin(frame, worldmapProviderPin) -end -worldmapPinsPool.resetterFunc = function(pinPool, pin) - FramePool_HideAndClearAnchors(pinPool, pin) - pin:OnReleased() - - pin.pinTemplate = nil - pin.owningMap = nil -end - --- register pin pool with the world map -WorldMapFrame.pinPools["HereBeDragonsPinsTemplate"] = worldmapPinsPool - --- provider base API -function worldmapProvider:RemoveAllData() - self:GetMap():RemoveAllPinsByTemplate("HereBeDragonsPinsTemplate") -end - -function worldmapProvider:RemovePinByIcon(icon) - for pin in self:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplate") do - if pin.icon == icon then - self:GetMap():RemovePin(pin) - end - end -end - -function worldmapProvider:RemovePinsByRef(ref) - for pin in self:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplate") do - if pin.icon and worldmapPinRegistry[ref][pin.icon] then - self:GetMap():RemovePin(pin) - end - end -end - -function worldmapProvider:RefreshAllData(fromOnShow) - self:RemoveAllData() - - for icon, data in pairs(worldmapPins) do - self:HandlePin(icon, data) - end -end - -function worldmapProvider:HandlePin(icon, data) - local uiMapID = self:GetMap():GetMapID() - - -- check for a valid map - if not uiMapID then return end - - local x, y - if uiMapID == WORLD_MAP_ID then - -- should this pin show on the world map? - if uiMapID ~= data.uiMapID and data.worldMapShowFlag ~= HBD_PINS_WORLDMAP_SHOW_WORLD then return end - - -- translate to the world map - x, y = HBD:GetAzerothWorldMapCoordinatesFromWorld(data.x, data.y, data.instanceID) - else - -- check that it matches the instance - if not HBD.mapData[uiMapID] or HBD.mapData[uiMapID].instance ~= data.instanceID then return end - - if uiMapID ~= data.uiMapID then - local mapType = HBD.mapData[uiMapID].mapType - if not data.uiMapID then - if mapType == Enum.UIMapType.Continent and data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_CONTINENT then - --pass - elseif mapType ~= Enum.UIMapType.Zone and mapType ~= Enum.UIMapType.Dungeon and mapType ~= Enum.UIMapType.Micro then - -- fail - return - end - else - local show = false - local parentMapID = HBD.mapData[data.uiMapID].parent - while parentMapID and HBD.mapData[parentMapID] do - if parentMapID == uiMapID then - local parentMapType = HBD.mapData[parentMapID].mapType - -- show on any parent zones if they are normal zones - if data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_PARENT and - (parentMapType == Enum.UIMapType.Zone or parentMapType == Enum.UIMapType.Dungeon or parentMapType == Enum.UIMapType.Micro) then - show = true - -- show on the continent - elseif data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_CONTINENT and - parentMapType == Enum.UIMapType.Continent then - show = true - end - break - -- worldmap is handled above already - else - parentMapID = HBD.mapData[parentMapID].parent - end - end - - if not show then return end - end - end - - -- translate coordinates - x, y = HBD:GetZoneCoordinatesFromWorld(data.x, data.y, uiMapID) - end - if x and y then - self:GetMap():AcquirePin("HereBeDragonsPinsTemplate", icon, x, y, data.frameLevelType) - end -end - --- map pin base API -function worldmapProviderPin:OnLoad() - self:UseFrameLevelType("PIN_FRAME_LEVEL_AREA_POI") - self:SetScalingLimits(1, 1.0, 1.2) -end - -function worldmapProviderPin:OnAcquired(icon, x, y, frameLevelType) - self:UseFrameLevelType(frameLevelType or "PIN_FRAME_LEVEL_AREA_POI") - self:SetPosition(x, y) - - self.icon = icon - icon:SetParent(self) - icon:ClearAllPoints() - icon:SetPoint("CENTER", self, "CENTER") - icon:Show() -end - -function worldmapProviderPin:OnReleased() - if self.icon then - self.icon:Hide() - self.icon:SetParent(UIParent) - self.icon:ClearAllPoints() - self.icon = nil - end -end - --- register with the world map -WorldMapFrame:AddDataProvider(worldmapProvider) - --- map event handling -local function UpdateMinimap() - UpdateMinimapZoom() - UpdateMinimapPins() -end - -local function UpdateWorldMap() - worldmapProvider:RefreshAllData() -end - -local last_update = 0 -local function OnUpdateHandler(frame, elapsed) - last_update = last_update + elapsed - if last_update > 1 or queueFullUpdate then - UpdateMinimapPins(queueFullUpdate) - last_update = 0 - queueFullUpdate = false - else - UpdateMinimapIconPosition() - end -end -pins.updateFrame:SetScript("OnUpdate", OnUpdateHandler) - -local function OnEventHandler(frame, event, ...) - if event == "CVAR_UPDATE" then - local cvar, value = ... - if cvar == "ROTATE_MINIMAP" then - rotateMinimap = (value == "1") - queueFullUpdate = true - end - elseif event == "MINIMAP_UPDATE_ZOOM" then - UpdateMinimap() - elseif event == "PLAYER_LOGIN" then - -- recheck cvars after login - rotateMinimap = GetCVar("rotateMinimap") == "1" - elseif event == "PLAYER_ENTERING_WORLD" then - UpdateMinimap() - UpdateWorldMap() - end -end - -pins.updateFrame:SetScript("OnEvent", OnEventHandler) -pins.updateFrame:UnregisterAllEvents() -pins.updateFrame:RegisterEvent("CVAR_UPDATE") -pins.updateFrame:RegisterEvent("MINIMAP_UPDATE_ZOOM") -pins.updateFrame:RegisterEvent("PLAYER_LOGIN") -pins.updateFrame:RegisterEvent("PLAYER_ENTERING_WORLD") - -HBD.RegisterCallback(pins, "PlayerZoneChanged", UpdateMinimap) - - ---- Add a icon to the minimap (x/y world coordinate version) --- Note: This API does not let you specify a map to limit the pin to, it'll be shown on all maps these coordinates are valid for. --- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) --- @param icon Icon Frame --- @param instanceID Instance ID of the map to add the icon to --- @param x X position in world coordinates --- @param y Y position in world coordinates --- @param floatOnEdge flag if the icon should float on the edge of the minimap when going out of range, or hide immediately (default false) -function pins:AddMinimapIconWorld(ref, icon, instanceID, x, y, floatOnEdge) - if not ref then - error(MAJOR..": AddMinimapIconWorld: 'ref' must not be nil") - end - if type(icon) ~= "table" or not icon.SetPoint then - error(MAJOR..": AddMinimapIconWorld: 'icon' must be a frame", 2) - end - if type(instanceID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then - error(MAJOR..": AddMinimapIconWorld: 'instanceID', 'x' and 'y' must be numbers", 2) - end - - if not minimapPinRegistry[ref] then - minimapPinRegistry[ref] = {} - end - - minimapPinRegistry[ref][icon] = true - - local t = minimapPins[icon] or newCachedTable() - t.instanceID = instanceID - t.x = x - t.y = y - t.floatOnEdge = floatOnEdge - t.uiMapID = nil - t.showInParentZone = nil - - minimapPins[icon] = t - queueFullUpdate = true - - icon:SetParent(pins.Minimap) -end - ---- Add a icon to the minimap (UiMapID zone coordinate version) --- The pin will only be shown on the map specified, or optionally its parent map if specified --- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) --- @param icon Icon Frame --- @param uiMapID uiMapID of the map to place the icon on --- @param x X position in local/point coordinates (0-1), relative to the zone --- @param y Y position in local/point coordinates (0-1), relative to the zone --- @param showInParentZone flag if the icon should be shown in its parent zone - ie. an icon in a microdungeon in the outdoor zone itself (default false) --- @param floatOnEdge flag if the icon should float on the edge of the minimap when going out of range, or hide immediately (default false) -function pins:AddMinimapIconMap(ref, icon, uiMapID, x, y, showInParentZone, floatOnEdge) - if not ref then - error(MAJOR..": AddMinimapIconMap: 'ref' must not be nil") - end - if type(icon) ~= "table" or not icon.SetPoint then - error(MAJOR..": AddMinimapIconMap: 'icon' must be a frame") - end - if type(uiMapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then - error(MAJOR..": AddMinimapIconMap: 'uiMapID', 'x' and 'y' must be numbers") - end - - -- convert to world coordinates and use our known adding function - local xCoord, yCoord, instanceID = HBD:GetWorldCoordinatesFromZone(x, y, uiMapID) - if not xCoord then return end - - self:AddMinimapIconWorld(ref, icon, instanceID, xCoord, yCoord, floatOnEdge) - - -- store extra information - minimapPins[icon].uiMapID = uiMapID - minimapPins[icon].showInParentZone = showInParentZone -end - ---- Check if a floating minimap icon is on the edge of the map --- @param icon the minimap icon -function pins:IsMinimapIconOnEdge(icon) - if not icon then return false end - local data = minimapPins[icon] - if not data then return nil end - - return data.onEdge -end - ---- Remove a minimap icon --- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) --- @param icon Icon Frame -function pins:RemoveMinimapIcon(ref, icon) - if not ref or not icon or not minimapPinRegistry[ref] then return end - minimapPinRegistry[ref][icon] = nil - if minimapPins[icon] then - recycle(minimapPins[icon]) - minimapPins[icon] = nil - activeMinimapPins[icon] = nil - end - icon:Hide() -end - ---- Remove all minimap icons belonging to your addon (as tracked by "ref") --- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) -function pins:RemoveAllMinimapIcons(ref) - if not ref or not minimapPinRegistry[ref] then return end - for icon in pairs(minimapPinRegistry[ref]) do - recycle(minimapPins[icon]) - minimapPins[icon] = nil - activeMinimapPins[icon] = nil - icon:Hide() - end - wipe(minimapPinRegistry[ref]) -end - ---- Set the minimap object to position the pins on. Needs to support the usual functions a Minimap-type object exposes. --- @param minimapObject The new minimap object, or nil to restore the default -function pins:SetMinimapObject(minimapObject) - pins.Minimap = minimapObject or Minimap - for pin in pairs(minimapPins) do - pin:SetParent(pins.Minimap) - end - UpdateMinimapPins(true) -end - --- world map constants --- show worldmap pin on its parent zone map (if any) -HBD_PINS_WORLDMAP_SHOW_PARENT = 1 --- show worldmap pin on the continent map -HBD_PINS_WORLDMAP_SHOW_CONTINENT = 2 --- show worldmap pin on the continent and world map -HBD_PINS_WORLDMAP_SHOW_WORLD = 3 - ---- Add a icon to the world map (x/y world coordinate version) --- Note: This API does not let you specify a map to limit the pin to, it'll be shown on all maps these coordinates are valid for. --- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) --- @param icon Icon Frame --- @param instanceID Instance ID of the map to add the icon to --- @param x X position in world coordinates --- @param y Y position in world coordinates --- @param showFlag Flag to control on which maps this pin will be shown --- @param frameLevel Optional Frame Level type registered with the WorldMapFrame, defaults to PIN_FRAME_LEVEL_AREA_POI -function pins:AddWorldMapIconWorld(ref, icon, instanceID, x, y, showFlag, frameLevel) - if not ref then - error(MAJOR..": AddWorldMapIconWorld: 'ref' must not be nil") - end - if type(icon) ~= "table" or not icon.SetPoint then - error(MAJOR..": AddWorldMapIconWorld: 'icon' must be a frame", 2) - end - if type(instanceID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then - error(MAJOR..": AddWorldMapIconWorld: 'instanceID', 'x' and 'y' must be numbers", 2) - end - - if not worldmapPinRegistry[ref] then - worldmapPinRegistry[ref] = {} - end - - worldmapPinRegistry[ref][icon] = true - - local t = worldmapPins[icon] or newCachedTable() - t.instanceID = instanceID - t.x = x - t.y = y - t.uiMapID = nil - t.worldMapShowFlag = showFlag or 0 - t.frameLevelType = frameLevel - - worldmapPins[icon] = t - - worldmapProvider:HandlePin(icon, t) -end - ---- Add a icon to the world map (uiMapID zone coordinate version) --- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) --- @param icon Icon Frame --- @param uiMapID uiMapID of the map to place the icon on --- @param x X position in local/point coordinates (0-1), relative to the zone --- @param y Y position in local/point coordinates (0-1), relative to the zone --- @param showFlag Flag to control on which maps this pin will be shown --- @param frameLevel Optional Frame Level type registered with the WorldMapFrame, defaults to PIN_FRAME_LEVEL_AREA_POI -function pins:AddWorldMapIconMap(ref, icon, uiMapID, x, y, showFlag, frameLevel) - if not ref then - error(MAJOR..": AddWorldMapIconMap: 'ref' must not be nil") - end - if type(icon) ~= "table" or not icon.SetPoint then - error(MAJOR..": AddWorldMapIconMap: 'icon' must be a frame") - end - if type(uiMapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then - error(MAJOR..": AddWorldMapIconMap: 'uiMapID', 'x' and 'y' must be numbers") - end - - -- convert to world coordinates - local xCoord, yCoord, instanceID = HBD:GetWorldCoordinatesFromZone(x, y, uiMapID) - if not xCoord then return end - - if not worldmapPinRegistry[ref] then - worldmapPinRegistry[ref] = {} - end - - worldmapPinRegistry[ref][icon] = true - - local t = worldmapPins[icon] or newCachedTable() - t.instanceID = instanceID - t.x = xCoord - t.y = yCoord - t.uiMapID = uiMapID - t.worldMapShowFlag = showFlag or 0 - t.frameLevelType = frameLevel - - worldmapPins[icon] = t - - worldmapProvider:HandlePin(icon, t) -end - ---- Remove a worldmap icon --- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) --- @param icon Icon Frame -function pins:RemoveWorldMapIcon(ref, icon) - if not ref or not icon or not worldmapPinRegistry[ref] then return end - worldmapPinRegistry[ref][icon] = nil - if worldmapPins[icon] then - recycle(worldmapPins[icon]) - worldmapPins[icon] = nil - end - worldmapProvider:RemovePinByIcon(icon) -end - ---- Remove all worldmap icons belonging to your addon (as tracked by "ref") --- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) -function pins:RemoveAllWorldMapIcons(ref) - if not ref or not worldmapPinRegistry[ref] then return end - for icon in pairs(worldmapPinRegistry[ref]) do - recycle(worldmapPins[icon]) - worldmapPins[icon] = nil - end - worldmapProvider:RemovePinsByRef(ref) - wipe(worldmapPinRegistry[ref]) -end - ---- Return the angle and distance from the player to the specified pin --- @param icon icon object (minimap or worldmap) --- @return angle, distance where angle is in radians and distance in yards -function pins:GetVectorToIcon(icon) - if not icon then return nil, nil end - local data = minimapPins[icon] or worldmapPins[icon] - if not data then return nil, nil end - - local x, y, instance = HBD:GetPlayerWorldPosition() - if not x or not y or instance ~= data.instanceID then return nil end - - return HBD:GetWorldVector(instance, x, y, data.x, data.y) -end +-- HereBeDragons-Pins is a library to show pins/icons on the world map and minimap + +local MAJOR, MINOR = "HereBeDragons-Pins-2.0", 8 +assert(LibStub, MAJOR .. " requires LibStub") + +local pins, _oldversion = LibStub:NewLibrary(MAJOR, MINOR) +if not pins then return end + +local HBD = LibStub("HereBeDragons-2.0") + +local MinimapRadiusAPI = C_Minimap and C_Minimap.GetViewRadius + +pins.updateFrame = pins.updateFrame or CreateFrame("Frame") + +-- storage for minimap pins +pins.minimapPins = pins.minimapPins or {} +pins.activeMinimapPins = pins.activeMinimapPins or {} +pins.minimapPinRegistry = pins.minimapPinRegistry or {} + +-- and worldmap pins +pins.worldmapPins = pins.worldmapPins or {} +pins.worldmapPinRegistry = pins.worldmapPinRegistry or {} +pins.worldmapPinsPool = pins.worldmapPinsPool or CreateFramePool("FRAME") +pins.worldmapProvider = pins.worldmapProvider or CreateFromMixins(MapCanvasDataProviderMixin) +pins.worldmapProviderPin = pins.worldmapProviderPin or CreateFromMixins(MapCanvasPinMixin) + +-- store a reference to the active minimap object +pins.Minimap = pins.Minimap or Minimap + +-- Data Constants +local WORLD_MAP_ID = 947 + +-- upvalue lua api +local cos, sin, max = math.cos, math.sin, math.max +local type, pairs = type, pairs + +-- upvalue wow api +local GetPlayerFacing = GetPlayerFacing + +-- upvalue data tables +local minimapPins = pins.minimapPins +local activeMinimapPins = pins.activeMinimapPins +local minimapPinRegistry = pins.minimapPinRegistry + +local worldmapPins = pins.worldmapPins +local worldmapPinRegistry = pins.worldmapPinRegistry +local worldmapPinsPool = pins.worldmapPinsPool +local worldmapProvider = pins.worldmapProvider +local worldmapProviderPin = pins.worldmapProviderPin + +local minimap_size = { + indoor = { + [0] = 300, -- scale + [1] = 240, -- 1.25 + [2] = 180, -- 5/3 + [3] = 120, -- 2.5 + [4] = 80, -- 3.75 + [5] = 50, -- 6 + }, + outdoor = { + [0] = 466 + 2/3, -- scale + [1] = 400, -- 7/6 + [2] = 333 + 1/3, -- 1.4 + [3] = 266 + 2/6, -- 1.75 + [4] = 200, -- 7/3 + [5] = 133 + 1/3, -- 3.5 + }, +} + +local minimap_shapes = { + -- { upper-left, lower-left, upper-right, lower-right } + ["SQUARE"] = { false, false, false, false }, + ["CORNER-TOPLEFT"] = { true, false, false, false }, + ["CORNER-TOPRIGHT"] = { false, false, true, false }, + ["CORNER-BOTTOMLEFT"] = { false, true, false, false }, + ["CORNER-BOTTOMRIGHT"] = { false, false, false, true }, + ["SIDE-LEFT"] = { true, true, false, false }, + ["SIDE-RIGHT"] = { false, false, true, true }, + ["SIDE-TOP"] = { true, false, true, false }, + ["SIDE-BOTTOM"] = { false, true, false, true }, + ["TRICORNER-TOPLEFT"] = { true, true, true, false }, + ["TRICORNER-TOPRIGHT"] = { true, false, true, true }, + ["TRICORNER-BOTTOMLEFT"] = { true, true, false, true }, + ["TRICORNER-BOTTOMRIGHT"] = { false, true, true, true }, +} + +local tableCache = setmetatable({}, {__mode='k'}) + +local function newCachedTable() + local t = next(tableCache) + if t then + tableCache[t] = nil + else + t = {} + end + return t +end + +local function recycle(t) + tableCache[t] = true +end + +------------------------------------------------------------------------------------------- +-- Minimap pin position logic + +-- minimap rotation +local rotateMinimap = GetCVar("rotateMinimap") == "1" + +-- is the minimap indoors or outdoors +local indoors = GetCVar("minimapZoom")+0 == pins.Minimap:GetZoom() and "outdoor" or "indoor" + +local minimapPinCount, queueFullUpdate = 0, false +local minimapScale, minimapShape, mapRadius, minimapWidth, minimapHeight, mapSin, mapCos +local lastZoom, lastFacing, lastXY, lastYY + +local function drawMinimapPin(pin, data) + local xDist, yDist = lastXY - data.x, lastYY - data.y + + -- handle rotation + if rotateMinimap then + local dx, dy = xDist, yDist + xDist = dx*mapCos - dy*mapSin + yDist = dx*mapSin + dy*mapCos + end + + -- adapt delta position to the map radius + local diffX = xDist / mapRadius + local diffY = yDist / mapRadius + + -- different minimap shapes + local isRound = true + if minimapShape and not (xDist == 0 or yDist == 0) then + isRound = (xDist < 0) and 1 or 3 + if yDist < 0 then + isRound = minimapShape[isRound] + else + isRound = minimapShape[isRound + 1] + end + end + + -- calculate distance from the center of the map + local dist + if isRound then + dist = (diffX*diffX + diffY*diffY) / 0.9^2 + else + dist = max(diffX*diffX, diffY*diffY) / 0.9^2 + end + + -- if distance > 1, then adapt node position to slide on the border + if dist > 1 and data.floatOnEdge then + dist = dist^0.5 + diffX = diffX/dist + diffY = diffY/dist + end + + if dist <= 1 or data.floatOnEdge then + pin:Show() + pin:ClearAllPoints() + pin:SetPoint("CENTER", pins.Minimap, "CENTER", diffX * minimapWidth, -diffY * minimapHeight) + data.onEdge = (dist > 1) + else + pin:Hide() + data.onEdge = nil + pin.keep = nil + end +end + +local function IsParentMap(originMapId, toCheckMapId) + local parentMapID = HBD.mapData[originMapId].parent + while parentMapID and HBD.mapData[parentMapID] do + local mapType = HBD.mapData[parentMapID].mapType + if mapType ~= Enum.UIMapType.Zone and mapType ~= Enum.UIMapType.Dungeon and mapType ~= Enum.UIMapType.Micro then + return false + end + if parentMapID == toCheckMapId then + return true + end + parentMapID = HBD.mapData[parentMapID].parent + end + return false +end + +local function UpdateMinimapPins(force) + -- get the current player position + local x, y, instanceID = HBD:GetPlayerWorldPosition() + local mapID = HBD:GetPlayerZone() + + -- get data from the API for calculations + local zoom = pins.Minimap:GetZoom() + local diffZoom = zoom ~= lastZoom + + -- for rotating minimap support + local facing + if rotateMinimap then + facing = GetPlayerFacing() + else + facing = lastFacing + end + + -- check for all values to be available (starting with 7.1.0, instances don't report coordinates) + if not x or not y or (rotateMinimap and not facing) then + minimapPinCount = 0 + for pin in pairs(activeMinimapPins) do + pin:Hide() + activeMinimapPins[pin] = nil + end + return + end + + local newScale = pins.Minimap:GetScale() + if minimapScale ~= newScale then + minimapScale = newScale + force = true + end + + if x ~= lastXY or y ~= lastYY or diffZoom or facing ~= lastFacing or force then + -- minimap information + minimapShape = GetMinimapShape and minimap_shapes[GetMinimapShape() or "ROUND"] + minimapWidth = pins.Minimap:GetWidth() / 2 + minimapHeight = pins.Minimap:GetHeight() / 2 + if MinimapRadiusAPI then + mapRadius = C_Minimap.GetViewRadius() + else + mapRadius = minimap_size[indoors][zoom] / 2 + end + + -- update upvalues for icon placement + lastZoom = zoom + lastFacing = facing + lastXY, lastYY = x, y + + if rotateMinimap then + mapSin = sin(facing) + mapCos = cos(facing) + end + + for pin, data in pairs(minimapPins) do + if data.instanceID == instanceID and (not data.uiMapID or data.uiMapID == mapID or (data.showInParentZone and IsParentMap(data.uiMapID, mapID))) then + activeMinimapPins[pin] = data + data.keep = true + -- draw the pin (this may reset data.keep if outside of the map) + drawMinimapPin(pin, data) + end + end + + minimapPinCount = 0 + for pin, data in pairs(activeMinimapPins) do + if not data.keep then + pin:Hide() + activeMinimapPins[pin] = nil + else + minimapPinCount = minimapPinCount + 1 + data.keep = nil + end + end + end +end + +local function UpdateMinimapIconPosition() + + -- get the current map zoom + local zoom = pins.Minimap:GetZoom() + local diffZoom = zoom ~= lastZoom + -- if the map zoom changed, run a full update sweep + if diffZoom then + UpdateMinimapPins() + return + end + + -- we have no active minimap pins, just return early + if minimapPinCount == 0 then return end + + local x, y = HBD:GetPlayerWorldPosition() + + -- for rotating minimap support + local facing + if rotateMinimap then + facing = GetPlayerFacing() + else + facing = lastFacing + end + + -- check for all values to be available (starting with 7.1.0, instances don't report coordinates) + if not x or not y or (rotateMinimap and not facing) then + UpdateMinimapPins() + return + end + + local refresh + local newScale = pins.Minimap:GetScale() + if minimapScale ~= newScale then + minimapScale = newScale + refresh = true + end + + if x ~= lastXY or y ~= lastYY or facing ~= lastFacing or refresh then + -- update radius of the map + if MinimapRadiusAPI then + mapRadius = C_Minimap.GetViewRadius() + else + mapRadius = minimap_size[indoors][zoom] / 2 + end + -- update upvalues for icon placement + lastXY, lastYY = x, y + lastFacing = facing + + if rotateMinimap then + mapSin = sin(facing) + mapCos = cos(facing) + end + + -- iterate all nodes and check if they are still in range of our minimap display + for pin, data in pairs(activeMinimapPins) do + -- update the position of the node + drawMinimapPin(pin, data) + end + end +end + +local function UpdateMinimapZoom() + if not MinimapRadiusAPI then + local zoom = pins.Minimap:GetZoom() + if GetCVar("minimapZoom") == GetCVar("minimapInsideZoom") then + pins.Minimap:SetZoom(zoom < 2 and zoom + 1 or zoom - 1) + end + indoors = GetCVar("minimapZoom")+0 == pins.Minimap:GetZoom() and "outdoor" or "indoor" + pins.Minimap:SetZoom(zoom) + end +end + +------------------------------------------------------------------------------------------- +-- WorldMap data provider + +-- setup pin pool +worldmapPinsPool.parent = WorldMapFrame:GetCanvas() +worldmapPinsPool.creationFunc = function(framePool) + local frame = CreateFrame(framePool.frameType, nil, framePool.parent) + frame:SetSize(1, 1) + return Mixin(frame, worldmapProviderPin) +end +worldmapPinsPool.resetterFunc = function(pinPool, pin) + FramePool_HideAndClearAnchors(pinPool, pin) + pin:OnReleased() + + pin.pinTemplate = nil + pin.owningMap = nil +end + +-- register pin pool with the world map +WorldMapFrame.pinPools["HereBeDragonsPinsTemplate"] = worldmapPinsPool + +-- provider base API +function worldmapProvider:RemoveAllData() + self:GetMap():RemoveAllPinsByTemplate("HereBeDragonsPinsTemplate") +end + +function worldmapProvider:RemovePinByIcon(icon) + for pin in self:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplate") do + if pin.icon == icon then + self:GetMap():RemovePin(pin) + end + end +end + +function worldmapProvider:RemovePinsByRef(ref) + for pin in self:GetMap():EnumeratePinsByTemplate("HereBeDragonsPinsTemplate") do + if pin.icon and worldmapPinRegistry[ref][pin.icon] then + self:GetMap():RemovePin(pin) + end + end +end + +function worldmapProvider:RefreshAllData(fromOnShow) + self:RemoveAllData() + + for icon, data in pairs(worldmapPins) do + self:HandlePin(icon, data) + end +end + +function worldmapProvider:HandlePin(icon, data) + local uiMapID = self:GetMap():GetMapID() + + -- check for a valid map + if not uiMapID then return end + + local x, y + if uiMapID == WORLD_MAP_ID then + -- should this pin show on the world map? + if uiMapID ~= data.uiMapID and data.worldMapShowFlag ~= HBD_PINS_WORLDMAP_SHOW_WORLD then return end + + -- translate to the world map + x, y = HBD:GetAzerothWorldMapCoordinatesFromWorld(data.x, data.y, data.instanceID) + else + -- check that it matches the instance + if not HBD.mapData[uiMapID] or HBD.mapData[uiMapID].instance ~= data.instanceID then return end + + if uiMapID ~= data.uiMapID then + local mapType = HBD.mapData[uiMapID].mapType + if not data.uiMapID then + if mapType == Enum.UIMapType.Continent and data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_CONTINENT then + --pass + elseif mapType ~= Enum.UIMapType.Zone and mapType ~= Enum.UIMapType.Dungeon and mapType ~= Enum.UIMapType.Micro then + -- fail + return + end + else + local show = false + local parentMapID = HBD.mapData[data.uiMapID].parent + while parentMapID and HBD.mapData[parentMapID] do + if parentMapID == uiMapID then + local parentMapType = HBD.mapData[parentMapID].mapType + -- show on any parent zones if they are normal zones + if data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_PARENT and + (parentMapType == Enum.UIMapType.Zone or parentMapType == Enum.UIMapType.Dungeon or parentMapType == Enum.UIMapType.Micro) then + show = true + -- show on the continent + elseif data.worldMapShowFlag >= HBD_PINS_WORLDMAP_SHOW_CONTINENT and + parentMapType == Enum.UIMapType.Continent then + show = true + end + break + -- worldmap is handled above already + else + parentMapID = HBD.mapData[parentMapID].parent + end + end + + if not show then return end + end + end + + -- translate coordinates + x, y = HBD:GetZoneCoordinatesFromWorld(data.x, data.y, uiMapID) + end + if x and y then + self:GetMap():AcquirePin("HereBeDragonsPinsTemplate", icon, x, y, data.frameLevelType) + end +end + +-- map pin base API +function worldmapProviderPin:OnLoad() + self:UseFrameLevelType("PIN_FRAME_LEVEL_AREA_POI") + self:SetScalingLimits(1, 1.0, 1.2) +end + +function worldmapProviderPin:OnAcquired(icon, x, y, frameLevelType) + self:UseFrameLevelType(frameLevelType or "PIN_FRAME_LEVEL_AREA_POI") + self:SetPosition(x, y) + + self.icon = icon + icon:SetParent(self) + icon:ClearAllPoints() + icon:SetPoint("CENTER", self, "CENTER") + icon:Show() +end + +function worldmapProviderPin:OnReleased() + if self.icon then + self.icon:Hide() + self.icon:SetParent(UIParent) + self.icon:ClearAllPoints() + self.icon = nil + end +end + +-- register with the world map +WorldMapFrame:AddDataProvider(worldmapProvider) + +-- map event handling +local function UpdateMinimap() + UpdateMinimapZoom() + UpdateMinimapPins() +end + +local function UpdateWorldMap() + worldmapProvider:RefreshAllData() +end + +local last_update = 0 +local function OnUpdateHandler(frame, elapsed) + last_update = last_update + elapsed + if last_update > 1 or queueFullUpdate then + UpdateMinimapPins(queueFullUpdate) + last_update = 0 + queueFullUpdate = false + else + UpdateMinimapIconPosition() + end +end +pins.updateFrame:SetScript("OnUpdate", OnUpdateHandler) + +local function OnEventHandler(frame, event, ...) + if event == "CVAR_UPDATE" then + local cvar, value = ... + if cvar == "ROTATE_MINIMAP" then + rotateMinimap = (value == "1") + queueFullUpdate = true + end + elseif event == "MINIMAP_UPDATE_ZOOM" then + UpdateMinimap() + elseif event == "PLAYER_LOGIN" then + -- recheck cvars after login + rotateMinimap = GetCVar("rotateMinimap") == "1" + elseif event == "PLAYER_ENTERING_WORLD" then + UpdateMinimap() + UpdateWorldMap() + end +end + +pins.updateFrame:SetScript("OnEvent", OnEventHandler) +pins.updateFrame:UnregisterAllEvents() +pins.updateFrame:RegisterEvent("CVAR_UPDATE") +pins.updateFrame:RegisterEvent("MINIMAP_UPDATE_ZOOM") +pins.updateFrame:RegisterEvent("PLAYER_LOGIN") +pins.updateFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + +HBD.RegisterCallback(pins, "PlayerZoneChanged", UpdateMinimap) + + +--- Add a icon to the minimap (x/y world coordinate version) +-- Note: This API does not let you specify a map to limit the pin to, it'll be shown on all maps these coordinates are valid for. +-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) +-- @param icon Icon Frame +-- @param instanceID Instance ID of the map to add the icon to +-- @param x X position in world coordinates +-- @param y Y position in world coordinates +-- @param floatOnEdge flag if the icon should float on the edge of the minimap when going out of range, or hide immediately (default false) +function pins:AddMinimapIconWorld(ref, icon, instanceID, x, y, floatOnEdge) + if not ref then + error(MAJOR..": AddMinimapIconWorld: 'ref' must not be nil", 2) + end + if type(icon) ~= "table" or not icon.SetPoint then + error(MAJOR..": AddMinimapIconWorld: 'icon' must be a frame", 2) + end + if type(instanceID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then + error(MAJOR..": AddMinimapIconWorld: 'instanceID', 'x' and 'y' must be numbers", 2) + end + + if not minimapPinRegistry[ref] then + minimapPinRegistry[ref] = {} + end + + minimapPinRegistry[ref][icon] = true + + local t = minimapPins[icon] or newCachedTable() + t.instanceID = instanceID + t.x = x + t.y = y + t.floatOnEdge = floatOnEdge + t.uiMapID = nil + t.showInParentZone = nil + + minimapPins[icon] = t + queueFullUpdate = true + + icon:SetParent(pins.Minimap) +end + +--- Add a icon to the minimap (UiMapID zone coordinate version) +-- The pin will only be shown on the map specified, or optionally its parent map if specified +-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) +-- @param icon Icon Frame +-- @param uiMapID uiMapID of the map to place the icon on +-- @param x X position in local/point coordinates (0-1), relative to the zone +-- @param y Y position in local/point coordinates (0-1), relative to the zone +-- @param showInParentZone flag if the icon should be shown in its parent zone - ie. an icon in a microdungeon in the outdoor zone itself (default false) +-- @param floatOnEdge flag if the icon should float on the edge of the minimap when going out of range, or hide immediately (default false) +function pins:AddMinimapIconMap(ref, icon, uiMapID, x, y, showInParentZone, floatOnEdge) + if not ref then + error(MAJOR..": AddMinimapIconMap: 'ref' must not be nil", 2) + end + if type(icon) ~= "table" or not icon.SetPoint then + error(MAJOR..": AddMinimapIconMap: 'icon' must be a frame", 2) + end + if type(uiMapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then + error(MAJOR..": AddMinimapIconMap: 'uiMapID', 'x' and 'y' must be numbers", 2) + end + + -- convert to world coordinates and use our known adding function + local xCoord, yCoord, instanceID = HBD:GetWorldCoordinatesFromZone(x, y, uiMapID) + if not xCoord then return end + + self:AddMinimapIconWorld(ref, icon, instanceID, xCoord, yCoord, floatOnEdge) + + -- store extra information + minimapPins[icon].uiMapID = uiMapID + minimapPins[icon].showInParentZone = showInParentZone +end + +--- Check if a floating minimap icon is on the edge of the map +-- @param icon the minimap icon +function pins:IsMinimapIconOnEdge(icon) + if not icon then return false end + local data = minimapPins[icon] + if not data then return nil end + + return data.onEdge +end + +--- Remove a minimap icon +-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) +-- @param icon Icon Frame +function pins:RemoveMinimapIcon(ref, icon) + if not ref or not icon or not minimapPinRegistry[ref] then return end + minimapPinRegistry[ref][icon] = nil + if minimapPins[icon] then + recycle(minimapPins[icon]) + minimapPins[icon] = nil + activeMinimapPins[icon] = nil + end + icon:Hide() +end + +--- Remove all minimap icons belonging to your addon (as tracked by "ref") +-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) +function pins:RemoveAllMinimapIcons(ref) + if not ref or not minimapPinRegistry[ref] then return end + for icon in pairs(minimapPinRegistry[ref]) do + recycle(minimapPins[icon]) + minimapPins[icon] = nil + activeMinimapPins[icon] = nil + icon:Hide() + end + wipe(minimapPinRegistry[ref]) +end + +--- Set the minimap object to position the pins on. Needs to support the usual functions a Minimap-type object exposes. +-- @param minimapObject The new minimap object, or nil to restore the default +function pins:SetMinimapObject(minimapObject) + pins.Minimap = minimapObject or Minimap + for pin in pairs(minimapPins) do + pin:SetParent(pins.Minimap) + end + UpdateMinimapPins(true) +end + +-- world map constants +-- show worldmap pin on its parent zone map (if any) +HBD_PINS_WORLDMAP_SHOW_PARENT = 1 +-- show worldmap pin on the continent map +HBD_PINS_WORLDMAP_SHOW_CONTINENT = 2 +-- show worldmap pin on the continent and world map +HBD_PINS_WORLDMAP_SHOW_WORLD = 3 + +--- Add a icon to the world map (x/y world coordinate version) +-- Note: This API does not let you specify a map to limit the pin to, it'll be shown on all maps these coordinates are valid for. +-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) +-- @param icon Icon Frame +-- @param instanceID Instance ID of the map to add the icon to +-- @param x X position in world coordinates +-- @param y Y position in world coordinates +-- @param showFlag Flag to control on which maps this pin will be shown +-- @param frameLevel Optional Frame Level type registered with the WorldMapFrame, defaults to PIN_FRAME_LEVEL_AREA_POI +function pins:AddWorldMapIconWorld(ref, icon, instanceID, x, y, showFlag, frameLevel) + if not ref then + error(MAJOR..": AddWorldMapIconWorld: 'ref' must not be nil", 2) + end + if type(icon) ~= "table" or not icon.SetPoint then + error(MAJOR..": AddWorldMapIconWorld: 'icon' must be a frame", 2) + end + if type(instanceID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then + error(MAJOR..": AddWorldMapIconWorld: 'instanceID', 'x' and 'y' must be numbers", 2) + end + if showFlag ~= nil and type(showFlag) ~= "number" then + error(MAJOR..": AddWorldMapIconWorld: 'showFlag' must be a number (or nil)", 2) + end + + if not worldmapPinRegistry[ref] then + worldmapPinRegistry[ref] = {} + end + + worldmapPinRegistry[ref][icon] = true + + local t = worldmapPins[icon] or newCachedTable() + t.instanceID = instanceID + t.x = x + t.y = y + t.uiMapID = nil + t.worldMapShowFlag = showFlag or 0 + t.frameLevelType = frameLevel + + worldmapPins[icon] = t + + worldmapProvider:HandlePin(icon, t) +end + +--- Add a icon to the world map (uiMapID zone coordinate version) +-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) +-- @param icon Icon Frame +-- @param uiMapID uiMapID of the map to place the icon on +-- @param x X position in local/point coordinates (0-1), relative to the zone +-- @param y Y position in local/point coordinates (0-1), relative to the zone +-- @param showFlag Flag to control on which maps this pin will be shown +-- @param frameLevel Optional Frame Level type registered with the WorldMapFrame, defaults to PIN_FRAME_LEVEL_AREA_POI +function pins:AddWorldMapIconMap(ref, icon, uiMapID, x, y, showFlag, frameLevel) + if not ref then + error(MAJOR..": AddWorldMapIconMap: 'ref' must not be nil", 2) + end + if type(icon) ~= "table" or not icon.SetPoint then + error(MAJOR..": AddWorldMapIconMap: 'icon' must be a frame", 2) + end + if type(uiMapID) ~= "number" or type(x) ~= "number" or type(y) ~= "number" then + error(MAJOR..": AddWorldMapIconMap: 'uiMapID', 'x' and 'y' must be numbers", 2) + end + if showFlag ~= nil and type(showFlag) ~= "number" then + error(MAJOR..": AddWorldMapIconMap: 'showFlag' must be a number (or nil)", 2) + end + + -- convert to world coordinates + local xCoord, yCoord, instanceID = HBD:GetWorldCoordinatesFromZone(x, y, uiMapID) + if not xCoord then return end + + if not worldmapPinRegistry[ref] then + worldmapPinRegistry[ref] = {} + end + + worldmapPinRegistry[ref][icon] = true + + local t = worldmapPins[icon] or newCachedTable() + t.instanceID = instanceID + t.x = xCoord + t.y = yCoord + t.uiMapID = uiMapID + t.worldMapShowFlag = showFlag or 0 + t.frameLevelType = frameLevel + + worldmapPins[icon] = t + + worldmapProvider:HandlePin(icon, t) +end + +--- Remove a worldmap icon +-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) +-- @param icon Icon Frame +function pins:RemoveWorldMapIcon(ref, icon) + if not ref or not icon or not worldmapPinRegistry[ref] then return end + worldmapPinRegistry[ref][icon] = nil + if worldmapPins[icon] then + recycle(worldmapPins[icon]) + worldmapPins[icon] = nil + end + worldmapProvider:RemovePinByIcon(icon) +end + +--- Remove all worldmap icons belonging to your addon (as tracked by "ref") +-- @param ref Reference to your addon to track the icon under (ie. your "self" or string identifier) +function pins:RemoveAllWorldMapIcons(ref) + if not ref or not worldmapPinRegistry[ref] then return end + for icon in pairs(worldmapPinRegistry[ref]) do + recycle(worldmapPins[icon]) + worldmapPins[icon] = nil + end + worldmapProvider:RemovePinsByRef(ref) + wipe(worldmapPinRegistry[ref]) +end + +--- Return the angle and distance from the player to the specified pin +-- @param icon icon object (minimap or worldmap) +-- @return angle, distance where angle is in radians and distance in yards +function pins:GetVectorToIcon(icon) + if not icon then return nil, nil end + local data = minimapPins[icon] or worldmapPins[icon] + if not data then return nil, nil end + + local x, y, instance = HBD:GetPlayerWorldPosition() + if not x or not y or instance ~= data.instanceID then return nil end + + return HBD:GetWorldVector(instance, x, y, data.x, data.y) +end diff --git a/Libraries/LibDBIcon-1.0/LibDBIcon-1.0.lua b/Libraries/LibDBIcon-1.0/LibDBIcon-1.0.lua index e865bdf..9c889de 100644 --- a/Libraries/LibDBIcon-1.0/LibDBIcon-1.0.lua +++ b/Libraries/LibDBIcon-1.0/LibDBIcon-1.0.lua @@ -1,470 +1,476 @@ - ------------------------------------------------------------------------ --- LibDBIcon-1.0 --- --- Allows addons to easily create a lightweight minimap icon as an alternative to heavier LDB displays. --- - -local DBICON10 = "LibDBIcon-1.0" -local DBICON10_MINOR = 43 -- Bump on changes -if not LibStub then error(DBICON10 .. " requires LibStub.") end -local ldb = LibStub("LibDataBroker-1.1", true) -if not ldb then error(DBICON10 .. " requires LibDataBroker-1.1.") end -local lib = LibStub:NewLibrary(DBICON10, DBICON10_MINOR) -if not lib then return end - -lib.objects = lib.objects or {} -lib.callbackRegistered = lib.callbackRegistered or nil -lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib) -lib.notCreated = lib.notCreated or {} -lib.radius = lib.radius or 5 -lib.tooltip = lib.tooltip or CreateFrame("GameTooltip", "LibDBIconTooltip", UIParent, "GameTooltipTemplate") -local next, Minimap = next, Minimap -local isDraggingButton = false - -function lib:IconCallback(event, name, key, value) - if lib.objects[name] then - if key == "icon" then - lib.objects[name].icon:SetTexture(value) - elseif key == "iconCoords" then - lib.objects[name].icon:UpdateCoord() - elseif key == "iconR" then - local _, g, b = lib.objects[name].icon:GetVertexColor() - lib.objects[name].icon:SetVertexColor(value, g, b) - elseif key == "iconG" then - local r, _, b = lib.objects[name].icon:GetVertexColor() - lib.objects[name].icon:SetVertexColor(r, value, b) - elseif key == "iconB" then - local r, g = lib.objects[name].icon:GetVertexColor() - lib.objects[name].icon:SetVertexColor(r, g, value) - end - end -end -if not lib.callbackRegistered then - ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__icon", "IconCallback") - ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconCoords", "IconCallback") - ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconR", "IconCallback") - ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconG", "IconCallback") - ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconB", "IconCallback") - lib.callbackRegistered = true -end - -local function getAnchors(frame) - local x, y = frame:GetCenter() - if not x or not y then return "CENTER" end - local hhalf = (x > UIParent:GetWidth()*2/3) and "RIGHT" or (x < UIParent:GetWidth()/3) and "LEFT" or "" - local vhalf = (y > UIParent:GetHeight()/2) and "TOP" or "BOTTOM" - return vhalf..hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP")..hhalf -end - -local function onEnter(self) - if isDraggingButton then return end - - for _, button in next, lib.objects do - if button.showOnMouseover then - button.fadeOut:Stop() - button:SetAlpha(1) - end - end - - local obj = self.dataObject - if obj.OnTooltipShow then - lib.tooltip:SetOwner(self, "ANCHOR_NONE") - lib.tooltip:SetPoint(getAnchors(self)) - obj.OnTooltipShow(lib.tooltip) - lib.tooltip:Show() - elseif obj.OnEnter then - obj.OnEnter(self) - end -end - -local function onLeave(self) - lib.tooltip:Hide() - - if not isDraggingButton then - for _, button in next, lib.objects do - if button.showOnMouseover then - button.fadeOut:Play() - end - end - end - - local obj = self.dataObject - if obj.OnLeave then - obj.OnLeave(self) - end -end - --------------------------------------------------------------------------------- - -local onDragStart, updatePosition - -do - local minimapShapes = { - ["ROUND"] = {true, true, true, true}, - ["SQUARE"] = {false, false, false, false}, - ["CORNER-TOPLEFT"] = {false, false, false, true}, - ["CORNER-TOPRIGHT"] = {false, false, true, false}, - ["CORNER-BOTTOMLEFT"] = {false, true, false, false}, - ["CORNER-BOTTOMRIGHT"] = {true, false, false, false}, - ["SIDE-LEFT"] = {false, true, false, true}, - ["SIDE-RIGHT"] = {true, false, true, false}, - ["SIDE-TOP"] = {false, false, true, true}, - ["SIDE-BOTTOM"] = {true, true, false, false}, - ["TRICORNER-TOPLEFT"] = {false, true, true, true}, - ["TRICORNER-TOPRIGHT"] = {true, false, true, true}, - ["TRICORNER-BOTTOMLEFT"] = {true, true, false, true}, - ["TRICORNER-BOTTOMRIGHT"] = {true, true, true, false}, - } - - local rad, cos, sin, sqrt, max, min = math.rad, math.cos, math.sin, math.sqrt, math.max, math.min - function updatePosition(button, position) - local angle = rad(position or 225) - local x, y, q = cos(angle), sin(angle), 1 - if x < 0 then q = q + 1 end - if y > 0 then q = q + 2 end - local minimapShape = GetMinimapShape and GetMinimapShape() or "ROUND" - local quadTable = minimapShapes[minimapShape] - local w = (Minimap:GetWidth() / 2) + lib.radius - local h = (Minimap:GetHeight() / 2) + lib.radius - if quadTable[q] then - x, y = x*w, y*h - else - local diagRadiusW = sqrt(2*(w)^2)-10 - local diagRadiusH = sqrt(2*(h)^2)-10 - x = max(-w, min(x*diagRadiusW, w)) - y = max(-h, min(y*diagRadiusH, h)) - end - button:SetPoint("CENTER", Minimap, "CENTER", x, y) - end -end - -local function onClick(self, b) - if self.dataObject.OnClick then - self.dataObject.OnClick(self, b) - end -end - -local function onMouseDown(self) - self.isMouseDown = true - self.icon:UpdateCoord() -end - -local function onMouseUp(self) - self.isMouseDown = false - self.icon:UpdateCoord() -end - -do - local deg, atan2 = math.deg, math.atan2 - local function onUpdate(self) - local mx, my = Minimap:GetCenter() - local px, py = GetCursorPosition() - local scale = Minimap:GetEffectiveScale() - px, py = px / scale, py / scale - local pos = 225 - if self.db then - pos = deg(atan2(py - my, px - mx)) % 360 - self.db.minimapPos = pos - else - pos = deg(atan2(py - my, px - mx)) % 360 - self.minimapPos = pos - end - updatePosition(self, pos) - end - - function onDragStart(self) - self:LockHighlight() - self.isMouseDown = true - self.icon:UpdateCoord() - self:SetScript("OnUpdate", onUpdate) - isDraggingButton = true - lib.tooltip:Hide() - for _, button in next, lib.objects do - if button.showOnMouseover then - button.fadeOut:Stop() - button:SetAlpha(1) - end - end - end -end - -local function onDragStop(self) - self:SetScript("OnUpdate", nil) - self.isMouseDown = false - self.icon:UpdateCoord() - self:UnlockHighlight() - isDraggingButton = false - for _, button in next, lib.objects do - if button.showOnMouseover then - button.fadeOut:Play() - end - end -end - -local defaultCoords = {0, 1, 0, 1} -local function updateCoord(self) - local coords = self:GetParent().dataObject.iconCoords or defaultCoords - local deltaX, deltaY = 0, 0 - if not self:GetParent().isMouseDown then - deltaX = (coords[2] - coords[1]) * 0.05 - deltaY = (coords[4] - coords[3]) * 0.05 - end - self:SetTexCoord(coords[1] + deltaX, coords[2] - deltaX, coords[3] + deltaY, coords[4] - deltaY) -end - -local function createButton(name, object, db) - local button = CreateFrame("Button", "LibDBIcon10_"..name, Minimap) - button.dataObject = object - button.db = db - button:SetFrameStrata("MEDIUM") - button:SetSize(31, 31) - button:SetFrameLevel(8) - button:RegisterForClicks("anyUp") - button:RegisterForDrag("LeftButton") - button:SetHighlightTexture(136477) --"Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight" - local overlay = button:CreateTexture(nil, "OVERLAY") - overlay:SetSize(53, 53) - overlay:SetTexture(136430) --"Interface\\Minimap\\MiniMap-TrackingBorder" - overlay:SetPoint("TOPLEFT") - local background = button:CreateTexture(nil, "BACKGROUND") - background:SetSize(20, 20) - background:SetTexture(136467) --"Interface\\Minimap\\UI-Minimap-Background" - background:SetPoint("TOPLEFT", 7, -5) - local icon = button:CreateTexture(nil, "ARTWORK") - icon:SetSize(17, 17) - icon:SetTexture(object.icon) - icon:SetPoint("TOPLEFT", 7, -6) - button.icon = icon - button.isMouseDown = false - - local r, g, b = icon:GetVertexColor() - icon:SetVertexColor(object.iconR or r, object.iconG or g, object.iconB or b) - - icon.UpdateCoord = updateCoord - icon:UpdateCoord() - - button:SetScript("OnEnter", onEnter) - button:SetScript("OnLeave", onLeave) - button:SetScript("OnClick", onClick) - if not db or not db.lock then - button:SetScript("OnDragStart", onDragStart) - button:SetScript("OnDragStop", onDragStop) - end - button:SetScript("OnMouseDown", onMouseDown) - button:SetScript("OnMouseUp", onMouseUp) - - button.fadeOut = button:CreateAnimationGroup() - local animOut = button.fadeOut:CreateAnimation("Alpha") - animOut:SetOrder(1) - animOut:SetDuration(0.2) - animOut:SetFromAlpha(1) - animOut:SetToAlpha(0) - animOut:SetStartDelay(1) - button.fadeOut:SetToFinalAlpha(true) - - lib.objects[name] = button - - if lib.loggedIn then - updatePosition(button, db and db.minimapPos) - if not db or not db.hide then - button:Show() - else - button:Hide() - end - end - lib.callbacks:Fire("LibDBIcon_IconCreated", button, name) -- Fire 'Icon Created' callback -end - --- We could use a metatable.__index on lib.objects, but then we'd create --- the icons when checking things like :IsRegistered, which is not necessary. -local function check(name) - if lib.notCreated[name] then - createButton(name, lib.notCreated[name][1], lib.notCreated[name][2]) - lib.notCreated[name] = nil - end -end - --- Wait a bit with the initial positioning to let any GetMinimapShape addons --- load up. -if not lib.loggedIn then - local f = CreateFrame("Frame") - f:SetScript("OnEvent", function(f) - for _, button in next, lib.objects do - updatePosition(button, button.db and button.db.minimapPos) - if not button.db or not button.db.hide then - button:Show() - else - button:Hide() - end - end - lib.loggedIn = true - f:SetScript("OnEvent", nil) - end) - f:RegisterEvent("PLAYER_LOGIN") -end - -local function getDatabase(name) - return lib.notCreated[name] and lib.notCreated[name][2] or lib.objects[name].db -end - -function lib:Register(name, object, db) - if not object.icon then error("Can't register LDB objects without icons set!") end - if lib.objects[name] or lib.notCreated[name] then error(DBICON10.. ": Object '".. name .."' is already registered.") end - if not db or not db.hide then - createButton(name, object, db) - else - lib.notCreated[name] = {object, db} - end -end - -function lib:Lock(name) - if not lib:IsRegistered(name) then return end - if lib.objects[name] then - lib.objects[name]:SetScript("OnDragStart", nil) - lib.objects[name]:SetScript("OnDragStop", nil) - end - local db = getDatabase(name) - if db then - db.lock = true - end -end - -function lib:Unlock(name) - if not lib:IsRegistered(name) then return end - if lib.objects[name] then - lib.objects[name]:SetScript("OnDragStart", onDragStart) - lib.objects[name]:SetScript("OnDragStop", onDragStop) - end - local db = getDatabase(name) - if db then - db.lock = nil - end -end - -function lib:Hide(name) - if not lib.objects[name] then return end - lib.objects[name]:Hide() -end - -function lib:Show(name) - check(name) - local button = lib.objects[name] - if button then - button:Show() - updatePosition(button, button.db and button.db.minimapPos or button.minimapPos) - end -end - -function lib:IsRegistered(name) - return (lib.objects[name] or lib.notCreated[name]) and true or false -end - -function lib:Refresh(name, db) - check(name) - local button = lib.objects[name] - if db then - button.db = db - end - updatePosition(button, button.db and button.db.minimapPos or button.minimapPos) - if not button.db or not button.db.hide then - button:Show() - else - button:Hide() - end - if not button.db or not button.db.lock then - button:SetScript("OnDragStart", onDragStart) - button:SetScript("OnDragStop", onDragStop) - else - button:SetScript("OnDragStart", nil) - button:SetScript("OnDragStop", nil) - end -end - -function lib:GetMinimapButton(name) - return lib.objects[name] -end - -do - local function OnMinimapEnter() - if isDraggingButton then return end - for _, button in next, lib.objects do - if button.showOnMouseover then - button.fadeOut:Stop() - button:SetAlpha(1) - end - end - end - local function OnMinimapLeave() - if isDraggingButton then return end - for _, button in next, lib.objects do - if button.showOnMouseover then - button.fadeOut:Play() - end - end - end - Minimap:HookScript("OnEnter", OnMinimapEnter) - Minimap:HookScript("OnLeave", OnMinimapLeave) - - function lib:ShowOnEnter(name, value) - local button = lib.objects[name] - if button then - if value then - button.showOnMouseover = true - button.fadeOut:Stop() - button:SetAlpha(0) - else - button.showOnMouseover = false - button.fadeOut:Stop() - button:SetAlpha(1) - end - end - end -end - -function lib:GetButtonList() - local t = {} - for name in next, lib.objects do - t[#t+1] = name - end - return t -end - -function lib:SetButtonRadius(radius) - if type(radius) == "number" then - lib.radius = radius - for _, button in next, lib.objects do - updatePosition(button, button.db and button.db.minimapPos or button.minimapPos) - end - end -end - -function lib:SetButtonToPosition(button, position) - updatePosition(lib.objects[button] or button, position) -end - --- Upgrade! -for name, button in next, lib.objects do - local db = getDatabase(name) - if not db or not db.lock then - button:SetScript("OnDragStart", onDragStart) - button:SetScript("OnDragStop", onDragStop) - end - button:SetScript("OnEnter", onEnter) - button:SetScript("OnLeave", onLeave) - button:SetScript("OnClick", onClick) - button:SetScript("OnMouseDown", onMouseDown) - button:SetScript("OnMouseUp", onMouseUp) - - if not button.fadeOut then -- Upgrade to 39 - button.fadeOut = button:CreateAnimationGroup() - local animOut = button.fadeOut:CreateAnimation("Alpha") - animOut:SetOrder(1) - animOut:SetDuration(0.2) - animOut:SetFromAlpha(1) - animOut:SetToAlpha(0) - animOut:SetStartDelay(1) - button.fadeOut:SetToFinalAlpha(true) - end -end -lib:SetButtonRadius(lib.radius) -- Upgrade to 40 + +----------------------------------------------------------------------- +-- LibDBIcon-1.0 +-- +-- Allows addons to easily create a lightweight minimap icon as an alternative to heavier LDB displays. +-- + +local DBICON10 = "LibDBIcon-1.0" +local DBICON10_MINOR = 44 -- Bump on changes +if not LibStub then error(DBICON10 .. " requires LibStub.") end +local ldb = LibStub("LibDataBroker-1.1", true) +if not ldb then error(DBICON10 .. " requires LibDataBroker-1.1.") end +local lib = LibStub:NewLibrary(DBICON10, DBICON10_MINOR) +if not lib then return end + +lib.objects = lib.objects or {} +lib.callbackRegistered = lib.callbackRegistered or nil +lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib) +lib.notCreated = lib.notCreated or {} +lib.radius = lib.radius or 5 +local next, Minimap, CreateFrame = next, Minimap, CreateFrame +lib.tooltip = lib.tooltip or CreateFrame("GameTooltip", "LibDBIconTooltip", UIParent, "GameTooltipTemplate") +local isDraggingButton = false + +function lib:IconCallback(event, name, key, value) + if lib.objects[name] then + if key == "icon" then + lib.objects[name].icon:SetTexture(value) + elseif key == "iconCoords" then + lib.objects[name].icon:UpdateCoord() + elseif key == "iconR" then + local _, g, b = lib.objects[name].icon:GetVertexColor() + lib.objects[name].icon:SetVertexColor(value, g, b) + elseif key == "iconG" then + local r, _, b = lib.objects[name].icon:GetVertexColor() + lib.objects[name].icon:SetVertexColor(r, value, b) + elseif key == "iconB" then + local r, g = lib.objects[name].icon:GetVertexColor() + lib.objects[name].icon:SetVertexColor(r, g, value) + end + end +end +if not lib.callbackRegistered then + ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__icon", "IconCallback") + ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconCoords", "IconCallback") + ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconR", "IconCallback") + ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconG", "IconCallback") + ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconB", "IconCallback") + lib.callbackRegistered = true +end + +local function getAnchors(frame) + local x, y = frame:GetCenter() + if not x or not y then return "CENTER" end + local hhalf = (x > UIParent:GetWidth()*2/3) and "RIGHT" or (x < UIParent:GetWidth()/3) and "LEFT" or "" + local vhalf = (y > UIParent:GetHeight()/2) and "TOP" or "BOTTOM" + return vhalf..hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP")..hhalf +end + +local function onEnter(self) + if isDraggingButton then return end + + for _, button in next, lib.objects do + if button.showOnMouseover then + button.fadeOut:Stop() + button:SetAlpha(1) + end + end + + local obj = self.dataObject + if obj.OnTooltipShow then + lib.tooltip:SetOwner(self, "ANCHOR_NONE") + lib.tooltip:SetPoint(getAnchors(self)) + obj.OnTooltipShow(lib.tooltip) + lib.tooltip:Show() + elseif obj.OnEnter then + obj.OnEnter(self) + end +end + +local function onLeave(self) + lib.tooltip:Hide() + + if not isDraggingButton then + for _, button in next, lib.objects do + if button.showOnMouseover then + button.fadeOut:Play() + end + end + end + + local obj = self.dataObject + if obj.OnLeave then + obj.OnLeave(self) + end +end + +-------------------------------------------------------------------------------- + +local onDragStart, updatePosition + +do + local minimapShapes = { + ["ROUND"] = {true, true, true, true}, + ["SQUARE"] = {false, false, false, false}, + ["CORNER-TOPLEFT"] = {false, false, false, true}, + ["CORNER-TOPRIGHT"] = {false, false, true, false}, + ["CORNER-BOTTOMLEFT"] = {false, true, false, false}, + ["CORNER-BOTTOMRIGHT"] = {true, false, false, false}, + ["SIDE-LEFT"] = {false, true, false, true}, + ["SIDE-RIGHT"] = {true, false, true, false}, + ["SIDE-TOP"] = {false, false, true, true}, + ["SIDE-BOTTOM"] = {true, true, false, false}, + ["TRICORNER-TOPLEFT"] = {false, true, true, true}, + ["TRICORNER-TOPRIGHT"] = {true, false, true, true}, + ["TRICORNER-BOTTOMLEFT"] = {true, true, false, true}, + ["TRICORNER-BOTTOMRIGHT"] = {true, true, true, false}, + } + + local rad, cos, sin, sqrt, max, min = math.rad, math.cos, math.sin, math.sqrt, math.max, math.min + function updatePosition(button, position) + local angle = rad(position or 225) + local x, y, q = cos(angle), sin(angle), 1 + if x < 0 then q = q + 1 end + if y > 0 then q = q + 2 end + local minimapShape = GetMinimapShape and GetMinimapShape() or "ROUND" + local quadTable = minimapShapes[minimapShape] + local w = (Minimap:GetWidth() / 2) + lib.radius + local h = (Minimap:GetHeight() / 2) + lib.radius + if quadTable[q] then + x, y = x*w, y*h + else + local diagRadiusW = sqrt(2*(w)^2)-10 + local diagRadiusH = sqrt(2*(h)^2)-10 + x = max(-w, min(x*diagRadiusW, w)) + y = max(-h, min(y*diagRadiusH, h)) + end + button:SetPoint("CENTER", Minimap, "CENTER", x, y) + end +end + +local function onClick(self, b) + if self.dataObject.OnClick then + self.dataObject.OnClick(self, b) + end +end + +local function onMouseDown(self) + self.isMouseDown = true + self.icon:UpdateCoord() +end + +local function onMouseUp(self) + self.isMouseDown = false + self.icon:UpdateCoord() +end + +do + local deg, atan2 = math.deg, math.atan2 + local function onUpdate(self) + local mx, my = Minimap:GetCenter() + local px, py = GetCursorPosition() + local scale = Minimap:GetEffectiveScale() + px, py = px / scale, py / scale + local pos = 225 + if self.db then + pos = deg(atan2(py - my, px - mx)) % 360 + self.db.minimapPos = pos + else + pos = deg(atan2(py - my, px - mx)) % 360 + self.minimapPos = pos + end + updatePosition(self, pos) + end + + function onDragStart(self) + self:LockHighlight() + self.isMouseDown = true + self.icon:UpdateCoord() + self:SetScript("OnUpdate", onUpdate) + isDraggingButton = true + lib.tooltip:Hide() + for _, button in next, lib.objects do + if button.showOnMouseover then + button.fadeOut:Stop() + button:SetAlpha(1) + end + end + end +end + +local function onDragStop(self) + self:SetScript("OnUpdate", nil) + self.isMouseDown = false + self.icon:UpdateCoord() + self:UnlockHighlight() + isDraggingButton = false + for _, button in next, lib.objects do + if button.showOnMouseover then + button.fadeOut:Play() + end + end +end + +local defaultCoords = {0, 1, 0, 1} +local function updateCoord(self) + local coords = self:GetParent().dataObject.iconCoords or defaultCoords + local deltaX, deltaY = 0, 0 + if not self:GetParent().isMouseDown then + deltaX = (coords[2] - coords[1]) * 0.05 + deltaY = (coords[4] - coords[3]) * 0.05 + end + self:SetTexCoord(coords[1] + deltaX, coords[2] - deltaX, coords[3] + deltaY, coords[4] - deltaY) +end + +local function createButton(name, object, db) + local button = CreateFrame("Button", "LibDBIcon10_"..name, Minimap) + button.dataObject = object + button.db = db + button:SetFrameStrata("MEDIUM") + if button.SetFixedFrameStrata then -- Classic support + button:SetFixedFrameStrata(true) + end + button:SetFrameLevel(8) + if button.SetFixedFrameLevel then -- Classic support + button:SetFixedFrameLevel(true) + end + button:SetSize(31, 31) + button:RegisterForClicks("anyUp") + button:RegisterForDrag("LeftButton") + button:SetHighlightTexture(136477) --"Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight" + local overlay = button:CreateTexture(nil, "OVERLAY") + overlay:SetSize(53, 53) + overlay:SetTexture(136430) --"Interface\\Minimap\\MiniMap-TrackingBorder" + overlay:SetPoint("TOPLEFT") + local background = button:CreateTexture(nil, "BACKGROUND") + background:SetSize(20, 20) + background:SetTexture(136467) --"Interface\\Minimap\\UI-Minimap-Background" + background:SetPoint("TOPLEFT", 7, -5) + local icon = button:CreateTexture(nil, "ARTWORK") + icon:SetSize(17, 17) + icon:SetTexture(object.icon) + icon:SetPoint("TOPLEFT", 7, -6) + button.icon = icon + button.isMouseDown = false + + local r, g, b = icon:GetVertexColor() + icon:SetVertexColor(object.iconR or r, object.iconG or g, object.iconB or b) + + icon.UpdateCoord = updateCoord + icon:UpdateCoord() + + button:SetScript("OnEnter", onEnter) + button:SetScript("OnLeave", onLeave) + button:SetScript("OnClick", onClick) + if not db or not db.lock then + button:SetScript("OnDragStart", onDragStart) + button:SetScript("OnDragStop", onDragStop) + end + button:SetScript("OnMouseDown", onMouseDown) + button:SetScript("OnMouseUp", onMouseUp) + + button.fadeOut = button:CreateAnimationGroup() + local animOut = button.fadeOut:CreateAnimation("Alpha") + animOut:SetOrder(1) + animOut:SetDuration(0.2) + animOut:SetFromAlpha(1) + animOut:SetToAlpha(0) + animOut:SetStartDelay(1) + button.fadeOut:SetToFinalAlpha(true) + + lib.objects[name] = button + + if lib.loggedIn then + updatePosition(button, db and db.minimapPos) + if not db or not db.hide then + button:Show() + else + button:Hide() + end + end + lib.callbacks:Fire("LibDBIcon_IconCreated", button, name) -- Fire 'Icon Created' callback +end + +-- We could use a metatable.__index on lib.objects, but then we'd create +-- the icons when checking things like :IsRegistered, which is not necessary. +local function check(name) + if lib.notCreated[name] then + createButton(name, lib.notCreated[name][1], lib.notCreated[name][2]) + lib.notCreated[name] = nil + end +end + +-- Wait a bit with the initial positioning to let any GetMinimapShape addons +-- load up. +if not lib.loggedIn then + local f = CreateFrame("Frame") + f:SetScript("OnEvent", function(f) + for _, button in next, lib.objects do + updatePosition(button, button.db and button.db.minimapPos) + if not button.db or not button.db.hide then + button:Show() + else + button:Hide() + end + end + lib.loggedIn = true + f:SetScript("OnEvent", nil) + end) + f:RegisterEvent("PLAYER_LOGIN") +end + +local function getDatabase(name) + return lib.notCreated[name] and lib.notCreated[name][2] or lib.objects[name].db +end + +function lib:Register(name, object, db) + if not object.icon then error("Can't register LDB objects without icons set!") end + if lib.objects[name] or lib.notCreated[name] then error(DBICON10.. ": Object '".. name .."' is already registered.") end + if not db or not db.hide then + createButton(name, object, db) + else + lib.notCreated[name] = {object, db} + end +end + +function lib:Lock(name) + if not lib:IsRegistered(name) then return end + if lib.objects[name] then + lib.objects[name]:SetScript("OnDragStart", nil) + lib.objects[name]:SetScript("OnDragStop", nil) + end + local db = getDatabase(name) + if db then + db.lock = true + end +end + +function lib:Unlock(name) + if not lib:IsRegistered(name) then return end + if lib.objects[name] then + lib.objects[name]:SetScript("OnDragStart", onDragStart) + lib.objects[name]:SetScript("OnDragStop", onDragStop) + end + local db = getDatabase(name) + if db then + db.lock = nil + end +end + +function lib:Hide(name) + if not lib.objects[name] then return end + lib.objects[name]:Hide() +end + +function lib:Show(name) + check(name) + local button = lib.objects[name] + if button then + button:Show() + updatePosition(button, button.db and button.db.minimapPos or button.minimapPos) + end +end + +function lib:IsRegistered(name) + return (lib.objects[name] or lib.notCreated[name]) and true or false +end + +function lib:Refresh(name, db) + check(name) + local button = lib.objects[name] + if db then + button.db = db + end + updatePosition(button, button.db and button.db.minimapPos or button.minimapPos) + if not button.db or not button.db.hide then + button:Show() + else + button:Hide() + end + if not button.db or not button.db.lock then + button:SetScript("OnDragStart", onDragStart) + button:SetScript("OnDragStop", onDragStop) + else + button:SetScript("OnDragStart", nil) + button:SetScript("OnDragStop", nil) + end +end + +function lib:GetMinimapButton(name) + return lib.objects[name] +end + +do + local function OnMinimapEnter() + if isDraggingButton then return end + for _, button in next, lib.objects do + if button.showOnMouseover then + button.fadeOut:Stop() + button:SetAlpha(1) + end + end + end + local function OnMinimapLeave() + if isDraggingButton then return end + for _, button in next, lib.objects do + if button.showOnMouseover then + button.fadeOut:Play() + end + end + end + Minimap:HookScript("OnEnter", OnMinimapEnter) + Minimap:HookScript("OnLeave", OnMinimapLeave) + + function lib:ShowOnEnter(name, value) + local button = lib.objects[name] + if button then + if value then + button.showOnMouseover = true + button.fadeOut:Stop() + button:SetAlpha(0) + else + button.showOnMouseover = false + button.fadeOut:Stop() + button:SetAlpha(1) + end + end + end +end + +function lib:GetButtonList() + local t = {} + for name in next, lib.objects do + t[#t+1] = name + end + return t +end + +function lib:SetButtonRadius(radius) + if type(radius) == "number" then + lib.radius = radius + for _, button in next, lib.objects do + updatePosition(button, button.db and button.db.minimapPos or button.minimapPos) + end + end +end + +function lib:SetButtonToPosition(button, position) + updatePosition(lib.objects[button] or button, position) +end + +-- Upgrade! +for name, button in next, lib.objects do + local db = getDatabase(name) + if not db or not db.lock then + button:SetScript("OnDragStart", onDragStart) + button:SetScript("OnDragStop", onDragStop) + end + button:SetScript("OnEnter", onEnter) + button:SetScript("OnLeave", onLeave) + button:SetScript("OnClick", onClick) + button:SetScript("OnMouseDown", onMouseDown) + button:SetScript("OnMouseUp", onMouseUp) + + if not button.fadeOut then -- Upgrade to 39 + button.fadeOut = button:CreateAnimationGroup() + local animOut = button.fadeOut:CreateAnimation("Alpha") + animOut:SetOrder(1) + animOut:SetDuration(0.2) + animOut:SetFromAlpha(1) + animOut:SetToAlpha(0) + animOut:SetStartDelay(1) + button.fadeOut:SetToFinalAlpha(true) + end +end +lib:SetButtonRadius(lib.radius) -- Upgrade to 40 diff --git a/Libraries/LibDataBroker-1.1/LibDataBroker-1.1.lua b/Libraries/LibDataBroker-1.1/LibDataBroker-1.1.lua index f47c0cd..4182f2e 100644 --- a/Libraries/LibDataBroker-1.1/LibDataBroker-1.1.lua +++ b/Libraries/LibDataBroker-1.1/LibDataBroker-1.1.lua @@ -1,90 +1,90 @@ - -assert(LibStub, "LibDataBroker-1.1 requires LibStub") -assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0") - -local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4) -if not lib then return end -oldminor = oldminor or 0 - - -lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib) -lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {} -local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks - -if oldminor < 2 then - lib.domt = { - __metatable = "access denied", - __index = function(self, key) return attributestorage[self] and attributestorage[self][key] end, - } -end - -if oldminor < 3 then - lib.domt.__newindex = function(self, key, value) - if not attributestorage[self] then attributestorage[self] = {} end - if attributestorage[self][key] == value then return end - attributestorage[self][key] = value - local name = namestorage[self] - if not name then return end - callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self) - callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self) - callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self) - callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self) - end -end - -if oldminor < 2 then - function lib:NewDataObject(name, dataobj) - if self.proxystorage[name] then return end - - if dataobj then - assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table") - self.attributestorage[dataobj] = {} - for i,v in pairs(dataobj) do - self.attributestorage[dataobj][i] = v - dataobj[i] = nil - end - end - dataobj = setmetatable(dataobj or {}, self.domt) - self.proxystorage[name], self.namestorage[dataobj] = dataobj, name - self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj) - return dataobj - end -end - -if oldminor < 1 then - function lib:DataObjectIterator() - return pairs(self.proxystorage) - end - - function lib:GetDataObjectByName(dataobjectname) - return self.proxystorage[dataobjectname] - end - - function lib:GetNameByDataObject(dataobject) - return self.namestorage[dataobject] - end -end - -if oldminor < 4 then - local next = pairs(attributestorage) - function lib:pairs(dataobject_or_name) - local t = type(dataobject_or_name) - assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)") - - local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name - assert(attributestorage[dataobj], "Data object not found") - - return next, attributestorage[dataobj], nil - end - - local ipairs_iter = ipairs(attributestorage) - function lib:ipairs(dataobject_or_name) - local t = type(dataobject_or_name) - assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)") - - local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name - assert(attributestorage[dataobj], "Data object not found") - - return ipairs_iter, attributestorage[dataobj], 0 - end -end + +assert(LibStub, "LibDataBroker-1.1 requires LibStub") +assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0") + +local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4) +if not lib then return end +oldminor = oldminor or 0 + + +lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib) +lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {} +local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks + +if oldminor < 2 then + lib.domt = { + __metatable = "access denied", + __index = function(self, key) return attributestorage[self] and attributestorage[self][key] end, + } +end + +if oldminor < 3 then + lib.domt.__newindex = function(self, key, value) + if not attributestorage[self] then attributestorage[self] = {} end + if attributestorage[self][key] == value then return end + attributestorage[self][key] = value + local name = namestorage[self] + if not name then return end + callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self) + end +end + +if oldminor < 2 then + function lib:NewDataObject(name, dataobj) + if self.proxystorage[name] then return end + + if dataobj then + assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table") + self.attributestorage[dataobj] = {} + for i,v in pairs(dataobj) do + self.attributestorage[dataobj][i] = v + dataobj[i] = nil + end + end + dataobj = setmetatable(dataobj or {}, self.domt) + self.proxystorage[name], self.namestorage[dataobj] = dataobj, name + self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj) + return dataobj + end +end + +if oldminor < 1 then + function lib:DataObjectIterator() + return pairs(self.proxystorage) + end + + function lib:GetDataObjectByName(dataobjectname) + return self.proxystorage[dataobjectname] + end + + function lib:GetNameByDataObject(dataobject) + return self.namestorage[dataobject] + end +end + +if oldminor < 4 then + local next = pairs(attributestorage) + function lib:pairs(dataobject_or_name) + local t = type(dataobject_or_name) + assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)") + + local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name + assert(attributestorage[dataobj], "Data object not found") + + return next, attributestorage[dataobj], nil + end + + local ipairs_iter = ipairs(attributestorage) + function lib:ipairs(dataobject_or_name) + local t = type(dataobject_or_name) + assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)") + + local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name + assert(attributestorage[dataobj], "Data object not found") + + return ipairs_iter, attributestorage[dataobj], 0 + end +end diff --git a/Libraries/LibStub/LibStub.lua b/Libraries/LibStub/LibStub.lua index 7e9b5cd..7e7b76d 100644 --- a/Libraries/LibStub/LibStub.lua +++ b/Libraries/LibStub/LibStub.lua @@ -1,51 +1,51 @@ --- $Id: LibStub.lua 103 2014-10-16 03:02:50Z mikk $ --- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/addons/libstub/ for more info --- LibStub is hereby placed in the Public Domain --- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke -local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! -local LibStub = _G[LIBSTUB_MAJOR] - --- Check to see is this version of the stub is obsolete -if not LibStub or LibStub.minor < LIBSTUB_MINOR then - LibStub = LibStub or {libs = {}, minors = {} } - _G[LIBSTUB_MAJOR] = LibStub - LibStub.minor = LIBSTUB_MINOR - - -- LibStub:NewLibrary(major, minor) - -- major (string) - the major version of the library - -- minor (string or number ) - the minor version of the library - -- - -- returns nil if a newer or same version of the lib is already present - -- returns empty library object or old library object if upgrade is needed - function LibStub:NewLibrary(major, minor) - assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") - minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") - - local oldminor = self.minors[major] - if oldminor and oldminor >= minor then return nil end - self.minors[major], self.libs[major] = minor, self.libs[major] or {} - return self.libs[major], oldminor - end - - -- LibStub:GetLibrary(major, [silent]) - -- major (string) - the major version of the library - -- silent (boolean) - if true, library is optional, silently return nil if its not found - -- - -- throws an error if the library can not be found (except silent is set) - -- returns the library object if found - function LibStub:GetLibrary(major, silent) - if not self.libs[major] and not silent then - error(("Cannot find a library instance of %q."):format(tostring(major)), 2) - end - return self.libs[major], self.minors[major] - end - - -- LibStub:IterateLibraries() - -- - -- Returns an iterator for the currently registered libraries - function LibStub:IterateLibraries() - return pairs(self.libs) - end - - setmetatable(LibStub, { __call = LibStub.GetLibrary }) -end +-- $Id: LibStub.lua 103 2014-10-16 03:02:50Z mikk $ +-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/addons/libstub/ for more info +-- LibStub is hereby placed in the Public Domain +-- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke +local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! +local LibStub = _G[LIBSTUB_MAJOR] + +-- Check to see is this version of the stub is obsolete +if not LibStub or LibStub.minor < LIBSTUB_MINOR then + LibStub = LibStub or {libs = {}, minors = {} } + _G[LIBSTUB_MAJOR] = LibStub + LibStub.minor = LIBSTUB_MINOR + + -- LibStub:NewLibrary(major, minor) + -- major (string) - the major version of the library + -- minor (string or number ) - the minor version of the library + -- + -- returns nil if a newer or same version of the lib is already present + -- returns empty library object or old library object if upgrade is needed + function LibStub:NewLibrary(major, minor) + assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") + minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") + + local oldminor = self.minors[major] + if oldminor and oldminor >= minor then return nil end + self.minors[major], self.libs[major] = minor, self.libs[major] or {} + return self.libs[major], oldminor + end + + -- LibStub:GetLibrary(major, [silent]) + -- major (string) - the major version of the library + -- silent (boolean) - if true, library is optional, silently return nil if its not found + -- + -- throws an error if the library can not be found (except silent is set) + -- returns the library object if found + function LibStub:GetLibrary(major, silent) + if not self.libs[major] and not silent then + error(("Cannot find a library instance of %q."):format(tostring(major)), 2) + end + return self.libs[major], self.minors[major] + end + + -- LibStub:IterateLibraries() + -- + -- Returns an iterator for the currently registered libraries + function LibStub:IterateLibraries() + return pairs(self.libs) + end + + setmetatable(LibStub, { __call = LibStub.GetLibrary }) +end diff --git a/Libraries/LibUIDropDownMenu/LibEasyMenu.lua b/Libraries/LibUIDropDownMenu/LibEasyMenu.lua index 678a584..731b6bb 100644 --- a/Libraries/LibUIDropDownMenu/LibEasyMenu.lua +++ b/Libraries/LibUIDropDownMenu/LibEasyMenu.lua @@ -1,47 +1,44 @@ ---$Id: LibEasyMenu.lua 30 2018-04-24 06:44:39Z arith $ --- Simplified Menu Display System --- This is a basic system for displaying a menu from a structure table. --- --- See UIDropDownMenu.lua for the menuList details. --- --- Args: --- menuList - menu table --- menuFrame - the UI frame to populate --- anchor - where to anchor the frame (e.g. CURSOR) --- x - x offset --- y - y offset --- displayMode - border type --- autoHideDelay - how long until the menu disappears --- --- --- ---------------------------------------------------------------------------- --- Localized Lua globals. --- ---------------------------------------------------------------------------- -local _G = getfenv(0) --- ---------------------------------------------------------------------------- -local MAJOR_VERSION = "LibEasyMenu" -local MINOR_VERSION = 90000 + tonumber(("$Rev: 30 $"):match("%d+")) - -local LibStub = _G.LibStub -if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end -local Lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) -if not Lib then return end - -function L_EasyMenu(menuList, menuFrame, anchor, x, y, displayMode, autoHideDelay ) - if ( displayMode == "MENU" ) then - menuFrame.displayMode = displayMode; - end - L_UIDropDownMenu_Initialize(menuFrame, L_EasyMenu_Initialize, displayMode, nil, menuList); - L_ToggleDropDownMenu(1, nil, menuFrame, anchor, x, y, menuList, nil, autoHideDelay); -end - -function L_EasyMenu_Initialize( frame, level, menuList ) - for index = 1, #menuList do - local value = menuList[index] - if (value.text) then - value.index = index; - L_UIDropDownMenu_AddButton( value, level ); - end - end -end - +--$Id: LibEasyMenu.lua 64 2020-11-18 13:13:15Z arithmandar $ +-- ////////////////////////////////////////////////////////////// +-- Notes: +-- Functions have been moved to under LibUIDropDownMenu.lua +-- New function calls are as below: +-- +-- - lib:EasyMenu(menuList, menuFrame, anchor, x, y, displayMode, autoHideDelay ) +-- - lib:EasyMenu_Initialize( frame, level, menuList ) +-- +-- ////////////////////////////////////////////////////////////// +-- Simplified Menu Display System +-- This is a basic system for displaying a menu from a structure table. +-- +-- See UIDropDownMenu.lua for the menuList details. +-- +-- Args: +-- menuList - menu table +-- menuFrame - the UI frame to populate +-- anchor - where to anchor the frame (e.g. CURSOR) +-- x - x offset +-- y - y offset +-- displayMode - border type +-- autoHideDelay - how long until the menu disappears +-- +-- +--[[ +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 + +function EasyMenu_Initialize( frame, 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 +]] diff --git a/Libraries/LibUIDropDownMenu/LibUIDropDownMenu.lua b/Libraries/LibUIDropDownMenu/LibUIDropDownMenu.lua index 0b1abbb..52eddba 100644 --- a/Libraries/LibUIDropDownMenu/LibUIDropDownMenu.lua +++ b/Libraries/LibUIDropDownMenu/LibUIDropDownMenu.lua @@ -1,1716 +1,1953 @@ --- $Id: LibUIDropDownMenu.lua 43 2019-09-02 14:14:50Z arith $ --- ---------------------------------------------------------------------------- --- Localized Lua globals. --- ---------------------------------------------------------------------------- -local _G = getfenv(0) -local tonumber, type, string, table = _G.tonumber, _G.type, _G.string, _G.table -local strsub, strlen, strmatch, gsub = _G.strsub, _G.strlen, _G.strmatch, _G.gsub -local max, match = _G.max, _G.match -local securecall, issecure = _G.securecall, _G.issecure -local wipe = table.wipe --- WoW -local CreateFrame, GetCursorPosition, GetCVar, GetScreenHeight, GetScreenWidth, PlaySound = _G.CreateFrame, _G.GetCursorPosition, _G.GetCVar, _G.GetScreenHeight, _G.GetScreenWidth, _G.PlaySound - --- ---------------------------------------------------------------------------- -local MAJOR_VERSION = "LibUIDropDownMenu-2.0" -local MINOR_VERSION = 90000 + tonumber(("$Rev: 43 $"):match("%d+")) - -local LibStub = _G.LibStub -if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end -local lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) -if not lib then return end - --- ////////////////////////////////////////////////////////////// -L_UIDROPDOWNMENU_MAXBUTTONS = 1; -L_UIDROPDOWNMENU_MAXLEVELS = 2; -L_UIDROPDOWNMENU_BUTTON_HEIGHT = 16; -L_UIDROPDOWNMENU_BORDER_HEIGHT = 15; --- The current open menu -L_UIDROPDOWNMENU_OPEN_MENU = nil; --- The current menu being initialized -L_UIDROPDOWNMENU_INIT_MENU = nil; --- Current level shown of the open menu -L_UIDROPDOWNMENU_MENU_LEVEL = 1; --- Current value of the open menu -L_UIDROPDOWNMENU_MENU_VALUE = nil; --- Time to wait to hide the menu -L_UIDROPDOWNMENU_SHOW_TIME = 2; --- Default dropdown text height -L_UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT = nil; --- List of open menus -L_OPEN_DROPDOWNMENUS = {}; - -local L_UIDropDownMenuDelegate = CreateFrame("FRAME"); - -function L_UIDropDownMenuDelegate_OnAttributeChanged (self, attribute, value) - if ( attribute == "createframes" and value == true ) then - L_UIDropDownMenu_CreateFrames(self:GetAttribute("createframes-level"), self:GetAttribute("createframes-index")); - elseif ( attribute == "initmenu" ) then - L_UIDROPDOWNMENU_INIT_MENU = value; - elseif ( attribute == "openmenu" ) then - L_UIDROPDOWNMENU_OPEN_MENU = value; - end -end - -L_UIDropDownMenuDelegate:SetScript("OnAttributeChanged", L_UIDropDownMenuDelegate_OnAttributeChanged); - -function L_UIDropDownMenu_InitializeHelper (frame) - -- This deals with the potentially tainted stuff! - if ( frame ~= L_UIDROPDOWNMENU_OPEN_MENU ) then - L_UIDROPDOWNMENU_MENU_LEVEL = 1; - end - - -- Set the frame that's being intialized - L_UIDropDownMenuDelegate:SetAttribute("initmenu", frame); - - -- Hide all the buttons - local button, dropDownList; - for i = 1, L_UIDROPDOWNMENU_MAXLEVELS, 1 do - dropDownList = _G["L_DropDownList"..i]; - if ( i >= L_UIDROPDOWNMENU_MENU_LEVEL or frame ~= L_UIDROPDOWNMENU_OPEN_MENU ) then - dropDownList.numButtons = 0; - dropDownList.maxWidth = 0; - for j=1, L_UIDROPDOWNMENU_MAXBUTTONS, 1 do - button = _G["L_DropDownList"..i.."Button"..j]; - button:Hide(); - end - dropDownList:Hide(); - end - end - frame:SetHeight(L_UIDROPDOWNMENU_BUTTON_HEIGHT * 2); -end - --- ////////////////////////////////////////////////////////////// --- L_UIDropDownMenuButtonTemplate -local function create_UIDropDownMenuButton(name, parent) - local f = CreateFrame("Button", name, parent or nil) - f:SetWidth(100) - f:SetHeight(16) - f:SetFrameLevel(f:GetParent():GetFrameLevel()+2) - - f.Highlight = f:CreateTexture(name.."Highlight", "BACKGROUND") - f.Highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") - f.Highlight:SetBlendMode("ADD") - f.Highlight:SetAllPoints() - f.Highlight:Hide() - - f.Check = f:CreateTexture(name.."Check", "ARTWORK") - f.Check:SetTexture("Interface\\Common\\UI-DropDownRadioChecks") - f.Check:SetSize(16, 16) - f.Check:SetPoint("LEFT", f, 0, 0) - f.Check:SetTexCoord(0, 0.5, 0.5, 1) - - f.UnCheck = f:CreateTexture(name.."UnCheck", "ARTWORK") - f.UnCheck:SetTexture("Interface\\Common\\UI-DropDownRadioChecks") - f.UnCheck:SetSize(16, 16) - f.UnCheck:SetPoint("LEFT", f, 0, 0) - f.UnCheck:SetTexCoord(0.5, 1, 0.5, 1) - - f.Icon = f:CreateTexture(name.."Icon", "ARTWORK") - f.Icon:SetSize(16, 16) - f.Icon:SetPoint("RIGHT", f, 0, 0) - f.Icon:Hide() - - -- ColorSwatch - local fcw = CreateFrame("Button", name.."ColorSwatch", f) - fcw:SetSize(16, 16) - fcw:SetPoint("RIGHT", f, -6, 0) - fcw:Hide() - fcw.SwatchBg = fcw:CreateTexture(name.."ColorSwatchSwatchBg", "BACKGROUND") - fcw.SwatchBg:SetVertexColor(1, 1, 1) - fcw.SwatchBg:SetWidth(14) - fcw.SwatchBg:SetHeight(14) - fcw.SwatchBg:SetPoint("CENTER", fcw, 0, 0) - local button1NormalTexture = fcw:CreateTexture(name.."ColorSwatchNormalTexture") - button1NormalTexture:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch") - button1NormalTexture:SetAllPoints() - fcw:SetNormalTexture(button1NormalTexture) - fcw:SetScript("OnClick", function(self, button, down) - CloseMenus() - L_UIDropDownMenuButton_OpenColorPicker(self:GetParent()) - end) - fcw:SetScript("OnEnter", function(self, motion) - L_CloseDropDownMenus(self:GetParent():GetParent():GetID() + 1) - _G[self:GetName().."SwatchBg"]:SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) - L_UIDropDownMenu_StopCounting(self:GetParent():GetParent()) - end) - fcw:SetScript("OnLeave", function(self, motion) - _G[self:GetName().."SwatchBg"]:SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b); - L_UIDropDownMenu_StartCounting(self:GetParent():GetParent()) - end) - f.ColorSwatch = fcw - - -- ExpandArrow - local fea = CreateFrame("Button", name.."ExpandArrow", f) - fea:SetSize(16, 16) - fea:SetPoint("RIGHT", f, 0, 0) - fea:Hide() - local button2NormalTexture = fea:CreateTexture(name.."ExpandArrowNormalTexture") - button2NormalTexture:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow") - button2NormalTexture:SetAllPoints() - fea:SetNormalTexture(button2NormalTexture) - fea:SetScript("OnClick", function(self, button, down) - L_ToggleDropDownMenu(self:GetParent():GetParent():GetID() + 1, self:GetParent().value, nil, nil, nil, nil, self:GetParent().menuList, self) - end) - fea:SetScript("OnEnter", function(self, motion) - local level = self:GetParent():GetParent():GetID() + 1 - L_CloseDropDownMenus(level) - if self:IsEnabled() then - local listFrame = _G["L_DropDownList"..level]; - if ( not listFrame or not listFrame:IsShown() or select(2, listFrame:GetPoint()) ~= self ) then - L_ToggleDropDownMenu(level, self:GetParent().value, nil, nil, nil, nil, self:GetParent().menuList, self) - end - end - L_UIDropDownMenu_StopCounting(self:GetParent():GetParent()) - end) - fea:SetScript("OnLeave", function(self, motion) - L_UIDropDownMenu_StartCounting(self:GetParent():GetParent()) - end) - f.ExpandArrow = fea - - -- InvisibleButton - local fib = CreateFrame("Button", name.."InvisibleButton", f) - fib:Hide() - fib:SetPoint("TOPLEFT", f, 0, 0) - fib:SetPoint("BOTTOMLEFT", f, 0, 0) - fib:SetPoint("RIGHT", fcw, "LEFT", 0, 0) - fib:SetScript("OnEnter", function(self, motion) - L_UIDropDownMenuButtonInvisibleButton_OnEnter(self) - end) - fib:SetScript("OnLeave", function(self, motion) - L_UIDropDownMenuButtonInvisibleButton_OnLeave(self) - end) - f.invisibleButton = fib - - -- UIDropDownMenuButton Scripts - f:SetScript("OnClick", function(self, button, down) - L_UIDropDownMenuButton_OnClick(self, button, down) - end) - f:SetScript("OnEnter", function(self, motion) - L_UIDropDownMenuButton_OnEnter(self) - end) - f:SetScript("OnLeave", function(self, motion) - L_UIDropDownMenuButton_OnLeave(self) - end) - f:SetScript("OnEnable", function(self) - self.invisibleButton:Hide() - end) - f:SetScript("OnDisable", function(self) - self.invisibleButton:Show() - end) - - local text1 = f:CreateFontString(name.."NormalText") - f:SetFontString(text1) - text1:SetPoint("LEFT", f, -5, 0) - f:SetNormalFontObject("GameFontHighlightSmallLeft") - f:SetHighlightFontObject("GameFontHighlightSmallLeft") - f:SetDisabledFontObject("GameFontDisableSmallLeft") - - return f -end - --- ////////////////////////////////////////////////////////////// --- L_UIDropDownListTemplate -local function creatre_UIDropDownList(name, parent) - local f = _G[name] or CreateFrame("Button", name) - f:SetParent(parent or nil) - f:Hide() - f:SetFrameStrata("DIALOG") - f:EnableMouse(true) - - f.Backdrop = _G[name.."Backdrop"] or CreateFrame("Frame", name.."Backdrop", f) - f.Backdrop:SetAllPoints() - f.Backdrop:SetBackdrop({ - bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark", - edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", - tile = true, - tileSize = 32, - edgeSize = 32, - insets = { left = 11, right = 12, top = 12, bottom = 9, }, - }) - - f.MenuBackdrop= _G[name.."MenuBackdrop"] or CreateFrame("Frame", name.."MenuBackdrop", f) - f.MenuBackdrop:SetAllPoints() - f.MenuBackdrop:SetBackdrop({ - bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", - edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", - tile = true, - tileSize = 16, - edgeSize = 16, - insets = { left = 5, right = 4, top = 4, bottom = 4, }, - }) - f.MenuBackdrop:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) - f.MenuBackdrop:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) - - f.Button1 = _G[name.."Button1"] or create_UIDropDownMenuButton(name.."Button1", f) - f.Button1:SetID(1) - - f:SetScript("OnClick", function(self) - self:Hide() - end) - f:SetScript("OnEnter", function(self, motion) - L_UIDropDownMenu_StopCounting(self, motion) - end) - f:SetScript("OnLeave", function(self, motion) - L_UIDropDownMenu_StartCounting(self, motion) - end) - f:SetScript("OnUpdate", function(self, elapsed) - L_UIDropDownMenu_OnUpdate(self, elapsed) - end) - f:SetScript("OnShow", function(self) - for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do - if (not self.noResize) then - _G[self:GetName().."Button"..i]:SetWidth(self.maxWidth); - end - end - if (not self.noResize) then - self:SetWidth(self.maxWidth+25); - end - self.showTimer = nil; - if ( self:GetID() > 1 ) then - self.parent = _G["L_DropDownList"..(self:GetID() - 1)]; - end - end) - f:SetScript("OnHide", function(self) - L_UIDropDownMenu_OnHide(self) - end) - - return f -end - --- ////////////////////////////////////////////////////////////// --- L_UIDropDownMenuTemplate -local function create_UIDropDownMenu(name, parent) - local f - if type(name) == "table" then - f = name - name = f:GetName() - else - f = CreateFrame("Frame", name, parent or nil) - end - f:SetSize(40, 32) - - f.Left = f:CreateTexture(name.."Left", "ARTWORK") - f.Left:SetTexture("Interface\\Glues\\CharacterCreate\\CharacterCreate-LabelFrame") - f.Left:SetSize(25, 64) - f.Left:SetPoint("TOPLEFT", f, 0, 17) - f.Left:SetTexCoord(0, 0.1953125, 0, 1) - - f.Middle = f:CreateTexture(name.."Middle", "ARTWORK") - f.Middle:SetTexture("Interface\\Glues\\CharacterCreate\\CharacterCreate-LabelFrame") - f.Middle:SetSize(115, 64) - f.Middle:SetPoint("LEFT", f.Left, "RIGHT") - f.Middle:SetTexCoord(0.1953125, 0.8046875, 0, 1) - - f.Right = f:CreateTexture(name.."Right", "ARTWORK") - f.Right:SetTexture("Interface\\Glues\\CharacterCreate\\CharacterCreate-LabelFrame") - f.Right:SetSize(25, 64) - f.Right:SetPoint("LEFT", f.Middle, "RIGHT") - f.Right:SetTexCoord(0.8046875, 1, 0, 1) - - f.Text = f:CreateFontString(name.."Text", "ARTWORK", "GameFontHighlightSmall") - f.Text:SetWordWrap(false) - f.Text:SetJustifyH("RIGHT") - f.Text:SetSize(0, 10) - f.Text:SetPoint("RIGHT", f.Right, -43, 2) - - f.Icon = f:CreateTexture(name.."Icon", "OVERLAY") - f.Icon:Hide() - f.Icon:SetSize(16, 16) - f.Icon:SetPoint("LEFT", 30, 2) - - f.Button = CreateFrame("Button", name.."Button", f) - f.Button:SetMotionScriptsWhileDisabled(true) - f.Button:SetSize(24, 24) - f.Button:SetPoint("TOPRIGHT", f.Right, -16, -18) - - f.Button.NormalTexture = f.Button:CreateTexture(name.."NormalTexture") - f.Button.NormalTexture:SetTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Up") - f.Button.NormalTexture:SetSize(24, 24) - f.Button.NormalTexture:SetPoint("RIGHT", f.Button, 0, 0) - f.Button:SetNormalTexture(f.Button.NormalTexture) - - f.Button.PushedTexture = f.Button:CreateTexture(name.."PushedTexture") - f.Button.PushedTexture:SetTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Down") - f.Button.PushedTexture:SetSize(24, 24) - f.Button.PushedTexture:SetPoint("RIGHT", f.Button, 0, 0) - f.Button:SetPushedTexture(f.Button.PushedTexture) - - f.Button.DisabledTexture = f.Button:CreateTexture(name.."DisabledTexture") - f.Button.DisabledTexture:SetTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Disabled") - f.Button.DisabledTexture:SetSize(24, 24) - f.Button.DisabledTexture:SetPoint("RIGHT", f.Button, 0, 0) - f.Button:SetDisabledTexture(f.Button.DisabledTexture) - - f.Button.HighlightTexture = f.Button:CreateTexture(name.."HighlightTexture") - f.Button.HighlightTexture:SetTexture("Interface\\Buttons\\UI-Common-MouseHilight") - f.Button.HighlightTexture:SetSize(24, 24) - f.Button.HighlightTexture:SetPoint("RIGHT", f.Button, 0, 0) - f.Button.HighlightTexture:SetBlendMode("ADD") - f.Button:SetHighlightTexture(f.Button.HighlightTexture) - - -- Button Script - f.Button:SetScript("OnEnter", function(self, motion) - local parent = self:GetParent() - local myscript = parent:GetScript("OnEnter") - if(myscript ~= nil) then - myscript(parent) - end - end) - f.Button:SetScript("OnLeave", function(self, motion) - local parent = self:GetParent() - local myscript = parent:GetScript("OnLeave") - if(myscript ~= nil) then - myscript(parent) - end - end) - f.Button:SetScript("OnClick", function(self, button, down) - L_ToggleDropDownMenu(nil, nil, self:GetParent()) - PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON) - end) - - -- UIDropDownMenu Script - f:SetScript("OnHide", function(self) - L_CloseDropDownMenus() - end) - - return f -end --- End of frame templates --- ////////////////////////////////////////////////////////////// - --- ////////////////////////////////////////////////////////////// --- Handling two frames from LibUIDropDownMenu.xml -local L_DropDownList1, L_DropDownList2 -do - L_DropDownList1 = creatre_UIDropDownList("L_DropDownList1") - L_DropDownList1:SetToplevel(true) - L_DropDownList1:SetFrameStrata("FULLSCREEN_DIALOG") - L_DropDownList1:Hide() - L_DropDownList1:SetID(1) - L_DropDownList1:SetSize(180, 10) - local fontName, fontHeight, fontFlags = _G["L_DropDownList1Button1NormalText"]:GetFont() - L_UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT = fontHeight - - L_DropDownList2 = creatre_UIDropDownList("L_DropDownList2") - L_DropDownList2:SetToplevel(true) - L_DropDownList2:SetFrameStrata("FULLSCREEN_DIALOG") - L_DropDownList2:Hide() - L_DropDownList2:SetID(2) - L_DropDownList2:SetSize(180, 10) -end - --- ////////////////////////////////////////////////////////////// --- Global function to replace L_UIDropDownMenuTemplate -function L_Create_UIDropDownMenu(name, parent) - return create_UIDropDownMenu(name, parent) -end - -local function GetChild(frame, name, key) - if (frame[key]) then - return frame[key]; - else - return _G[name..key]; - end -end - -function L_UIDropDownMenu_Initialize(frame, initFunction, displayMode, level, menuList) - frame.menuList = menuList; - - securecall("L_UIDropDownMenu_InitializeHelper", frame); - - -- Set the initialize function and call it. The initFunction populates the dropdown list. - if ( initFunction ) then - L_UIDropDownMenu_SetInitializeFunction(frame, initFunction); - initFunction(frame, level, frame.menuList); - end - - --master frame - if(level == nil) then - level = 1; - end - - local dropDownList = _G["L_DropDownList"..level]; - dropDownList.dropdown = frame; - dropDownList.shouldRefresh = true; - - L_UIDropDownMenu_SetDisplayMode(frame, displayMode); -end - -function L_UIDropDownMenu_SetInitializeFunction(frame, initFunction) - frame.initialize = initFunction; -end - -function L_UIDropDownMenu_SetDisplayMode(frame, displayMode) - -- Change appearance based on the displayMode - -- Note: this is a one time change based on previous behavior. - if ( displayMode == "MENU" ) then - local name = frame:GetName(); - GetChild(frame, name, "Left"):Hide(); - GetChild(frame, name, "Middle"):Hide(); - GetChild(frame, name, "Right"):Hide(); - local button = GetChild(frame, name, "Button"); - local buttonName = button:GetName(); - GetChild(button, buttonName, "NormalTexture"):SetTexture(nil); - GetChild(button, buttonName, "DisabledTexture"):SetTexture(nil); - GetChild(button, buttonName, "PushedTexture"):SetTexture(nil); - GetChild(button, buttonName, "HighlightTexture"):SetTexture(nil); - local text = GetChild(frame, name, "Text"); - - button:ClearAllPoints(); - button:SetPoint("LEFT", text, "LEFT", -9, 0); - button:SetPoint("RIGHT", text, "RIGHT", 6, 0); - frame.displayMode = "MENU"; - end -end - -function L_UIDropDownMenu_RefreshDropDownSize(self) - self.maxWidth = L_UIDropDownMenu_GetMaxButtonWidth(self); - self:SetWidth(self.maxWidth + 25); - - for i=1, L_UIDROPDOWNMENU_MAXBUTTONS, 1 do - local icon = _G[self:GetName().."Button"..i.."Icon"]; - - if ( icon.tFitDropDownSizeX ) then - icon:SetWidth(self.maxWidth - 5); - end - end -end - --- If dropdown is visible then see if its timer has expired, if so hide the frame -function L_UIDropDownMenu_OnUpdate(self, elapsed) - if ( self.shouldRefresh ) then - L_UIDropDownMenu_RefreshDropDownSize(self); - self.shouldRefresh = false; - end - - if ( not self.showTimer or not self.isCounting ) then - return; - elseif ( self.showTimer < 0 ) then - self:Hide(); - self.showTimer = nil; - self.isCounting = nil; - else - self.showTimer = self.showTimer - elapsed; - end -end - --- Start the countdown on a frame -function L_UIDropDownMenu_StartCounting(frame) - if ( frame.parent ) then - L_UIDropDownMenu_StartCounting(frame.parent); - else - frame.showTimer = L_UIDROPDOWNMENU_SHOW_TIME; - frame.isCounting = 1; - end -end - --- Stop the countdown on a frame -function L_UIDropDownMenu_StopCounting(frame) - if ( frame.parent ) then - L_UIDropDownMenu_StopCounting(frame.parent); - else - frame.isCounting = nil; - end -end - -function L_UIDropDownMenuButtonInvisibleButton_OnEnter(self) - L_UIDropDownMenu_StopCounting(self:GetParent():GetParent()); - L_CloseDropDownMenus(self:GetParent():GetParent():GetID() + 1); - local parent = self:GetParent(); - if ( parent.tooltipTitle and parent.tooltipWhileDisabled) then - if ( parent.tooltipOnButton ) then - GameTooltip:SetOwner(parent, "ANCHOR_RIGHT"); - GameTooltip_SetTitle(GameTooltip, parent.tooltipTitle); - if parent.tooltipInstruction then - GameTooltip_AddInstructionLine(GameTooltip, parent.tooltipInstruction); - end - if parent.tooltipText then - GameTooltip_AddNormalLine(GameTooltip, parent.tooltipText, true); - end - if parent.tooltipWarning then - GameTooltip_AddColoredLine(GameTooltip, parent.tooltipWarning, RED_FONT_COLOR, true); - end - GameTooltip:Show(); - else - GameTooltip_AddNewbieTip(parent, parent.tooltipTitle, 1.0, 1.0, 1.0, parent.tooltipText, 1); - end - end -end - -function L_UIDropDownMenuButtonInvisibleButton_OnLeave(self) - L_UIDropDownMenu_StartCounting(self:GetParent():GetParent()); - GameTooltip:Hide(); -end - -function L_UIDropDownMenuButton_OnEnter(self) - if ( self.hasArrow ) then - local level = self:GetParent():GetID() + 1; - local listFrame = _G["L_DropDownList"..level]; - if ( not listFrame or not listFrame:IsShown() or select(2, listFrame:GetPoint()) ~= self ) then - L_ToggleDropDownMenu(self:GetParent():GetID() + 1, self.value, nil, nil, nil, nil, self.menuList, self); - end - else - L_CloseDropDownMenus(self:GetParent():GetID() + 1); - end - self.Highlight:Show(); - L_UIDropDownMenu_StopCounting(self:GetParent()); - if ( self.tooltipTitle and not self.noTooltipWhileEnabled ) then - if ( self.tooltipOnButton ) then - GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); - GameTooltip_SetTitle(GameTooltip, self.tooltipTitle); - if self.tooltipText then - GameTooltip_AddNormalLine(GameTooltip, self.tooltipText, true); - end - GameTooltip:Show(); - else - GameTooltip_AddNewbieTip(self, self.tooltipTitle, 1.0, 1.0, 1.0, self.tooltipText, 1); - end - end - - if ( self.mouseOverIcon ~= nil ) then - self.Icon:SetTexture(self.mouseOverIcon); - self.Icon:Show(); - end -end - -function L_UIDropDownMenuButton_OnLeave(self) - self.Highlight:Hide(); - L_UIDropDownMenu_StartCounting(self:GetParent()); - GameTooltip:Hide(); - - if ( self.mouseOverIcon ~= nil ) then - if ( self.icon ~= nil ) then - self.Icon:SetTexture(self.icon); - else - self.Icon:Hide(); - end - end -end - ---[[ -List of button attributes -====================================================== -info.text = [STRING] -- The text of the button -info.value = [ANYTHING] -- The value that L_UIDROPDOWNMENU_MENU_VALUE is set to when the button is clicked -info.func = [function()] -- The function that is called when you click the button -info.checked = [nil, true, function] -- Check the button if true or function returns true -info.isNotRadio = [nil, true] -- Check the button uses radial image if false check box image if true -info.isTitle = [nil, true] -- If it's a title the button is disabled and the font color is set to yellow -info.disabled = [nil, true] -- Disable the button and show an invisible button that still traps the mouseover event so menu doesn't time out -info.tooltipWhileDisabled = [nil, 1] -- Show the tooltip, even when the button is disabled. -info.hasArrow = [nil, true] -- Show the expand arrow for multilevel menus -info.hasColorSwatch = [nil, true] -- Show color swatch or not, for color selection -info.r = [1 - 255] -- Red color value of the color swatch -info.g = [1 - 255] -- Green color value of the color swatch -info.b = [1 - 255] -- Blue color value of the color swatch -info.colorCode = [STRING] -- "|cAARRGGBB" embedded hex value of the button text color. Only used when button is enabled -info.swatchFunc = [function()] -- Function called by the color picker on color change -info.hasOpacity = [nil, 1] -- Show the opacity slider on the colorpicker frame -info.opacity = [0.0 - 1.0] -- Percentatge of the opacity, 1.0 is fully shown, 0 is transparent -info.opacityFunc = [function()] -- Function called by the opacity slider when you change its value -info.cancelFunc = [function(previousValues)] -- Function called by the colorpicker when you click the cancel button (it takes the previous values as its argument) -info.notClickable = [nil, 1] -- Disable the button and color the font white -info.notCheckable = [nil, 1] -- Shrink the size of the buttons and don't display a check box -info.owner = [Frame] -- Dropdown frame that "owns" the current dropdownlist -info.keepShownOnClick = [nil, 1] -- Don't hide the dropdownlist after a button is clicked -info.tooltipTitle = [nil, STRING] -- Title of the tooltip shown on mouseover -info.tooltipText = [nil, STRING] -- Text of the tooltip shown on mouseover -info.tooltipOnButton = [nil, 1] -- Show the tooltip attached to the button instead of as a Newbie tooltip. -info.justifyH = [nil, "CENTER"] -- Justify button text -info.arg1 = [ANYTHING] -- This is the first argument used by info.func -info.arg2 = [ANYTHING] -- This is the second argument used by info.func -info.fontObject = [FONT] -- font object replacement for Normal and Highlight -info.menuTable = [TABLE] -- This contains an array of info tables to be displayed as a child menu -info.noClickSound = [nil, 1] -- Set to 1 to suppress the sound when clicking the button. The sound only plays if .func is set. -info.padding = [nil, NUMBER] -- Number of pixels to pad the text on the right side -info.leftPadding = [nil, NUMBER] -- Number of pixels to pad the button on the left side -info.minWidth = [nil, NUMBER] -- Minimum width for this line -info.customFrame = frame -- Allows this button to be a completely custom frame, should inherit from L_UIDropDownCustomMenuEntryTemplate and override appropriate methods. -info.icon = [TEXTURE] -- An icon for the button. -info.mouseOverIcon = [TEXTURE] -- An override icon when a button is moused over. -]] - -function L_UIDropDownMenu_CreateInfo() - return {}; -end - -function L_UIDropDownMenu_CreateFrames(level, index) - while ( level > L_UIDROPDOWNMENU_MAXLEVELS ) do - L_UIDROPDOWNMENU_MAXLEVELS = L_UIDROPDOWNMENU_MAXLEVELS + 1; - --local newList = CreateFrame("Button", "L_DropDownList"..L_UIDROPDOWNMENU_MAXLEVELS, nil, "L_UIDropDownListTemplate"); - local newList = creatre_UIDropDownList("L_DropDownList"..L_UIDROPDOWNMENU_MAXLEVELS) - newList:SetFrameStrata("FULLSCREEN_DIALOG"); - newList:SetToplevel(true); - newList:Hide(); - newList:SetID(L_UIDROPDOWNMENU_MAXLEVELS); - newList:SetWidth(180) - newList:SetHeight(10) - for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do - --local newButton = CreateFrame("Button", "L_DropDownList"..L_UIDROPDOWNMENU_MAXLEVELS.."Button"..i, newList, "L_UIDropDownMenuButtonTemplate"); - local newButton = create_UIDropDownMenuButton("L_DropDownList"..L_UIDROPDOWNMENU_MAXLEVELS.."Button"..i, newList) - newButton:SetID(i); - end - end - - while ( index > L_UIDROPDOWNMENU_MAXBUTTONS ) do - L_UIDROPDOWNMENU_MAXBUTTONS = L_UIDROPDOWNMENU_MAXBUTTONS + 1; - for i=1, L_UIDROPDOWNMENU_MAXLEVELS do - --local newButton = CreateFrame("Button", "L_DropDownList"..i.."Button"..L_UIDROPDOWNMENU_MAXBUTTONS, _G["L_DropDownList"..i], "L_UIDropDownMenuButtonTemplate"); - local newButton = create_UIDropDownMenuButton("L_DropDownList"..i.."Button"..L_UIDROPDOWNMENU_MAXBUTTONS, _G["L_DropDownList"..i]) - newButton:SetID(L_UIDROPDOWNMENU_MAXBUTTONS); - end - end -end - -function L_UIDropDownMenu_AddSeparator(level) - local separatorInfo = { - hasArrow = false; - dist = 0; - isTitle = true; - isUninteractable = true; - notCheckable = true; - iconOnly = true; - icon = "Interface\\Common\\UI-TooltipDivider-Transparent"; - tCoordLeft = 0; - tCoordRight = 1; - tCoordTop = 0; - tCoordBottom = 1; - tSizeX = 0; - tSizeY = 8; - tFitDropDownSizeX = true; - iconInfo = { - tCoordLeft = 0, - tCoordRight = 1, - tCoordTop = 0, - tCoordBottom = 1, - tSizeX = 0, - tSizeY = 8, - tFitDropDownSizeX = true - }, - }; - - L_UIDropDownMenu_AddButton(separatorInfo, level); -end - -function L_UIDropDownMenu_AddButton(info, level) - --[[ - Might to uncomment this if there are performance issues - if ( not L_UIDROPDOWNMENU_OPEN_MENU ) then - return; - end - ]] - if ( not level ) then - level = 1; - end - - local listFrame = _G["L_DropDownList"..level]; - local index = listFrame and (listFrame.numButtons + 1) or 1; - local width; - - L_UIDropDownMenuDelegate:SetAttribute("createframes-level", level); - L_UIDropDownMenuDelegate:SetAttribute("createframes-index", index); - L_UIDropDownMenuDelegate:SetAttribute("createframes", true); - - listFrame = listFrame or _G["L_DropDownList"..level]; - local listFrameName = listFrame:GetName(); - - -- Set the number of buttons in the listframe - listFrame.numButtons = index; - - local button = _G[listFrameName.."Button"..index]; - local normalText = _G[button:GetName().."NormalText"]; - local icon = _G[button:GetName().."Icon"]; - -- This button is used to capture the mouse OnEnter/OnLeave events if the dropdown button is disabled, since a disabled button doesn't receive any events - -- This is used specifically for drop down menu time outs - local invisibleButton = _G[button:GetName().."InvisibleButton"]; - - -- Default settings - button:SetDisabledFontObject(GameFontDisableSmallLeft); - invisibleButton:Hide(); - button:Enable(); - - -- If not clickable then disable the button and set it white - if ( info.notClickable ) then - info.disabled = true; - button:SetDisabledFontObject(GameFontHighlightSmallLeft); - end - - -- Set the text color and disable it if its a title - if ( info.isTitle ) then - info.disabled = true; - button:SetDisabledFontObject(GameFontNormalSmallLeft); - end - - -- Disable the button if disabled and turn off the color code - if ( info.disabled ) then - button:Disable(); - invisibleButton:Show(); - info.colorCode = nil; - end - - -- If there is a color for a disabled line, set it - if( info.disablecolor ) then - info.colorCode = info.disablecolor; - end - - -- Configure button - if ( info.text ) then - -- look for inline color code this is only if the button is enabled - if ( info.colorCode ) then - button:SetText(info.colorCode..info.text.."|r"); - else - button:SetText(info.text); - end - - -- Set icon - if ( info.icon or info.mouseOverIcon ) then - icon:SetSize(16,16); - icon:SetTexture(info.icon); - icon:ClearAllPoints(); - icon:SetPoint("RIGHT"); - - if ( info.tCoordLeft ) then - icon:SetTexCoord(info.tCoordLeft, info.tCoordRight, info.tCoordTop, info.tCoordBottom); - else - icon:SetTexCoord(0, 1, 0, 1); - end - icon:Show(); - else - icon:Hide(); - end - - -- Check to see if there is a replacement font - if ( info.fontObject ) then - button:SetNormalFontObject(info.fontObject); - button:SetHighlightFontObject(info.fontObject); - else - button:SetNormalFontObject(GameFontHighlightSmallLeft); - button:SetHighlightFontObject(GameFontHighlightSmallLeft); - end - else - button:SetText(""); - icon:Hide(); - end - - button.iconOnly = nil; - button.icon = nil; - button.iconInfo = nil; - - if (info.iconInfo) then - icon.tFitDropDownSizeX = info.iconInfo.tFitDropDownSizeX; - else - icon.tFitDropDownSizeX = nil; - end - if (info.iconOnly and info.icon) then - button.iconOnly = true; - button.icon = info.icon; - button.iconInfo = info.iconInfo; - - L_UIDropDownMenu_SetIconImage(icon, info.icon, info.iconInfo); - icon:ClearAllPoints(); - icon:SetPoint("LEFT"); - end - - -- Pass through attributes - button.func = info.func; - button.owner = info.owner; - button.hasOpacity = info.hasOpacity; - button.opacity = info.opacity; - button.opacityFunc = info.opacityFunc; - button.cancelFunc = info.cancelFunc; - button.swatchFunc = info.swatchFunc; - button.keepShownOnClick = info.keepShownOnClick; - button.tooltipTitle = info.tooltipTitle; - button.tooltipText = info.tooltipText; - button.tooltipInstruction = info.tooltipInstruction; - button.tooltipWarning = info.tooltipWarning; - button.arg1 = info.arg1; - button.arg2 = info.arg2; - button.hasArrow = info.hasArrow; - button.hasColorSwatch = info.hasColorSwatch; - button.notCheckable = info.notCheckable; - button.menuList = info.menuList; - button.tooltipWhileDisabled = info.tooltipWhileDisabled; - button.noTooltipWhileEnabled = info.noTooltipWhileEnabled; - button.tooltipOnButton = info.tooltipOnButton; - button.noClickSound = info.noClickSound; - button.padding = info.padding; - button.icon = info.icon; - button.mouseOverIcon = info.mouseOverIcon; - - if ( info.value ) then - button.value = info.value; - elseif ( info.text ) then - button.value = info.text; - else - button.value = nil; - end - - local expandArrow = _G[listFrameName.."Button"..index.."ExpandArrow"]; - expandArrow:SetShown(info.hasArrow); - expandArrow:SetEnabled(not info.disabled); - - -- If not checkable move everything over to the left to fill in the gap where the check would be - local xPos = 5; - local yPos = -((button:GetID() - 1) * L_UIDROPDOWNMENU_BUTTON_HEIGHT) - L_UIDROPDOWNMENU_BORDER_HEIGHT; - local displayInfo = normalText; - if (info.iconOnly) then - displayInfo = icon; - end - - displayInfo:ClearAllPoints(); - if ( info.notCheckable ) then - if ( info.justifyH and info.justifyH == "CENTER" ) then - displayInfo:SetPoint("CENTER", button, "CENTER", -7, 0); - else - displayInfo:SetPoint("LEFT", button, "LEFT", 0, 0); - end - xPos = xPos + 10; - - else - xPos = xPos + 12; - displayInfo:SetPoint("LEFT", button, "LEFT", 20, 0); - end - - -- Adjust offset if displayMode is menu - local frame = L_UIDROPDOWNMENU_OPEN_MENU; - if ( frame and frame.displayMode == "MENU" ) then - if ( not info.notCheckable ) then - xPos = xPos - 6; - end - end - - -- If no open frame then set the frame to the currently initialized frame - frame = frame or L_UIDROPDOWNMENU_INIT_MENU; - - if ( info.leftPadding ) then - xPos = xPos + info.leftPadding; - end - button:SetPoint("TOPLEFT", button:GetParent(), "TOPLEFT", xPos, yPos); - - -- See if button is selected by id or name - if ( frame ) then - if ( L_UIDropDownMenu_GetSelectedName(frame) ) then - if ( button:GetText() == L_UIDropDownMenu_GetSelectedName(frame) ) then - info.checked = 1; - end - elseif ( L_UIDropDownMenu_GetSelectedID(frame) ) then - if ( button:GetID() == L_UIDropDownMenu_GetSelectedID(frame) ) then - info.checked = 1; - end - elseif ( L_UIDropDownMenu_GetSelectedValue(frame) ) then - if ( button.value == L_UIDropDownMenu_GetSelectedValue(frame) ) then - info.checked = 1; - end - end - end - - if not info.notCheckable then - local check = _G[listFrameName.."Button"..index.."Check"]; - local uncheck = _G[listFrameName.."Button"..index.."UnCheck"]; - if ( info.disabled ) then - check:SetDesaturated(true); - check:SetAlpha(0.5); - uncheck:SetDesaturated(true); - uncheck:SetAlpha(0.5); - else - check:SetDesaturated(false); - check:SetAlpha(1); - uncheck:SetDesaturated(false); - uncheck:SetAlpha(1); - end - - if info.customCheckIconAtlas or info.customCheckIconTexture then - check:SetTexCoord(0, 1, 0, 1); - uncheck:SetTexCoord(0, 1, 0, 1); - - if info.customCheckIconAtlas then - check:SetAtlas(info.customCheckIconAtlas); - uncheck:SetAtlas(info.customUncheckIconAtlas or info.customCheckIconAtlas); - else - check:SetTexture(info.customCheckIconTexture); - uncheck:SetTexture(info.customUncheckIconTexture or info.customCheckIconTexture); - end - elseif info.isNotRadio then - check:SetTexCoord(0.0, 0.5, 0.0, 0.5); - check:SetTexture("Interface\\Common\\UI-DropDownRadioChecks"); - uncheck:SetTexCoord(0.5, 1.0, 0.0, 0.5); - uncheck:SetTexture("Interface\\Common\\UI-DropDownRadioChecks"); - else - check:SetTexCoord(0.0, 0.5, 0.5, 1.0); - check:SetTexture("Interface\\Common\\UI-DropDownRadioChecks"); - uncheck:SetTexCoord(0.5, 1.0, 0.5, 1.0); - uncheck:SetTexture("Interface\\Common\\UI-DropDownRadioChecks"); - end - - -- Checked can be a function now - local checked = info.checked; - if ( type(checked) == "function" ) then - checked = checked(button); - end - - -- Show the check if checked - if ( checked ) then - button:LockHighlight(); - check:Show(); - uncheck:Hide(); - else - button:UnlockHighlight(); - check:Hide(); - uncheck:Show(); - end - else - _G[listFrameName.."Button"..index.."Check"]:Hide(); - _G[listFrameName.."Button"..index.."UnCheck"]:Hide(); - end - button.checked = info.checked; - - -- If has a colorswatch, show it and vertex color it - local colorSwatch = _G[listFrameName.."Button"..index.."ColorSwatch"]; - if ( info.hasColorSwatch ) then - _G["L_DropDownList"..level.."Button"..index.."ColorSwatch".."NormalTexture"]:SetVertexColor(info.r, info.g, info.b); - button.r = info.r; - button.g = info.g; - button.b = info.b; - colorSwatch:Show(); - else - colorSwatch:Hide(); - end - - L_UIDropDownMenu_CheckAddCustomFrame(listFrame, button, info); - - button:SetShown(button.customFrame == nil); - - button.minWidth = info.minWidth; - - width = max(L_UIDropDownMenu_GetButtonWidth(button), info.minWidth or 0); - --Set maximum button width - if ( width > listFrame.maxWidth ) then - listFrame.maxWidth = width; - end - - -- Set the height of the listframe - listFrame:SetHeight((index * L_UIDROPDOWNMENU_BUTTON_HEIGHT) + (L_UIDROPDOWNMENU_BORDER_HEIGHT * 2)); -end - -function L_UIDropDownMenu_CheckAddCustomFrame(self, button, info) - local customFrame = info.customFrame; - button.customFrame = customFrame; - if customFrame then - customFrame:SetOwningButton(button); - customFrame:ClearAllPoints(); - customFrame:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0); - customFrame:Show(); - - L_UIDropDownMenu_RegisterCustomFrame(self, customFrame); - end -end - -function L_UIDropDownMenu_RegisterCustomFrame(self, customFrame) - self.customFrames = self.customFrames or {} - table.insert(self.customFrames, customFrame); -end - -function L_UIDropDownMenu_GetMaxButtonWidth(self) - local maxWidth = 0; - for i=1, self.numButtons do - local button = _G[self:GetName().."Button"..i]; - local width = L_UIDropDownMenu_GetButtonWidth(button); - if ( width > maxWidth ) then - maxWidth = width; - end - end - return maxWidth; -end - -function L_UIDropDownMenu_GetButtonWidth(button) - local minWidth = button.minWidth or 0; - if button.customFrame and button.customFrame:IsShown() then - return math.max(minWidth, button.customFrame:GetPreferredEntryWidth()); - end - - if not button:IsShown() then - return 0; - end - - local width; - local buttonName = button:GetName(); - local icon = _G[buttonName.."Icon"]; - local normalText = _G[buttonName.."NormalText"]; - - if ( button.iconOnly and icon ) then - width = icon:GetWidth(); - elseif ( normalText and normalText:GetText() ) then - width = normalText:GetWidth() + 40; - - if ( button.icon ) then - -- Add padding for the icon - width = width + 10; - end - else - return minWidth; - end - - -- Add padding if has and expand arrow or color swatch - if ( button.hasArrow or button.hasColorSwatch ) then - width = width + 10; - end - if ( button.notCheckable ) then - width = width - 30; - end - if ( button.padding ) then - width = width + button.padding; - end - - return math.max(minWidth, width); -end - -function L_UIDropDownMenu_Refresh(frame, useValue, dropdownLevel) - local maxWidth = 0; - local somethingChecked = nil; - if ( not dropdownLevel ) then - dropdownLevel = L_UIDROPDOWNMENU_MENU_LEVEL; - end - - local listFrame = _G["L_DropDownList"..dropdownLevel]; - listFrame.numButtons = listFrame.numButtons or 0; - -- Just redraws the existing menu - for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do - local button = _G["L_DropDownList"..dropdownLevel.."Button"..i]; - local checked = nil; - - if(i <= listFrame.numButtons) then - -- See if checked or not - if ( L_UIDropDownMenu_GetSelectedName(frame) ) then - if ( button:GetText() == L_UIDropDownMenu_GetSelectedName(frame) ) then - checked = 1; - end - elseif ( L_UIDropDownMenu_GetSelectedID(frame) ) then - if ( button:GetID() == L_UIDropDownMenu_GetSelectedID(frame) ) then - checked = 1; - end - elseif ( L_UIDropDownMenu_GetSelectedValue(frame) ) then - if ( button.value == L_UIDropDownMenu_GetSelectedValue(frame) ) then - checked = 1; - end - end - end - if (button.checked and type(button.checked) == "function") then - checked = button.checked(button); - end - - if not button.notCheckable and button:IsShown() then - -- If checked show check image - local checkImage = _G["L_DropDownList"..dropdownLevel.."Button"..i.."Check"]; - local uncheckImage = _G["L_DropDownList"..dropdownLevel.."Button"..i.."UnCheck"]; - if ( checked ) then - somethingChecked = true; - local icon = GetChild(frame, frame:GetName(), "Icon"); - if (button.iconOnly and icon and button.icon) then - L_UIDropDownMenu_SetIconImage(icon, button.icon, button.iconInfo); - elseif ( useValue ) then - L_UIDropDownMenu_SetText(frame, button.value); - icon:Hide(); - else - L_UIDropDownMenu_SetText(frame, button:GetText()); - icon:Hide(); - end - button:LockHighlight(); - checkImage:Show(); - uncheckImage:Hide(); - else - button:UnlockHighlight(); - checkImage:Hide(); - uncheckImage:Show(); - end - end - - if ( button:IsShown() ) then - local width = L_UIDropDownMenu_GetButtonWidth(button); - if ( width > maxWidth ) then - maxWidth = width; - end - end - end - if(somethingChecked == nil) then - L_UIDropDownMenu_SetText(frame, VIDEO_QUALITY_LABEL6); - end - if (not frame.noResize) then - for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do - local button = _G["L_DropDownList"..dropdownLevel.."Button"..i]; - button:SetWidth(maxWidth); - end - L_UIDropDownMenu_RefreshDropDownSize(_G["L_DropDownList"..dropdownLevel]); - end -end - -function L_UIDropDownMenu_RefreshAll(frame, useValue) - for dropdownLevel = L_UIDROPDOWNMENU_MENU_LEVEL, 2, -1 do - local listFrame = _G["L_DropDownList"..dropdownLevel]; - if ( listFrame:IsShown() ) then - L_UIDropDownMenu_Refresh(frame, nil, dropdownLevel); - end - end - -- useValue is the text on the dropdown, only needs to be set once - L_UIDropDownMenu_Refresh(frame, useValue, 1); -end - -function L_UIDropDownMenu_SetIconImage(icon, texture, info) - icon:SetTexture(texture); - if ( info.tCoordLeft ) then - icon:SetTexCoord(info.tCoordLeft, info.tCoordRight, info.tCoordTop, info.tCoordBottom); - else - icon:SetTexCoord(0, 1, 0, 1); - end - if ( info.tSizeX ) then - icon:SetWidth(info.tSizeX); - else - icon:SetWidth(16); - end - if ( info.tSizeY ) then - icon:SetHeight(info.tSizeY); - else - icon:SetHeight(16); - end - icon:Show(); -end - -function L_UIDropDownMenu_SetSelectedName(frame, name, useValue) - frame.selectedName = name; - frame.selectedID = nil; - frame.selectedValue = nil; - L_UIDropDownMenu_Refresh(frame, useValue); -end - -function L_UIDropDownMenu_SetSelectedValue(frame, value, useValue) - -- useValue will set the value as the text, not the name - frame.selectedName = nil; - frame.selectedID = nil; - frame.selectedValue = value; - L_UIDropDownMenu_Refresh(frame, useValue); -end - -function L_UIDropDownMenu_SetSelectedID(frame, id, useValue) - frame.selectedID = id; - frame.selectedName = nil; - frame.selectedValue = nil; - L_UIDropDownMenu_Refresh(frame, useValue); -end - -function L_UIDropDownMenu_GetSelectedName(frame) - return frame.selectedName; -end - -function L_UIDropDownMenu_GetSelectedID(frame) - if ( frame.selectedID ) then - return frame.selectedID; - else - -- If no explicit selectedID then try to send the id of a selected value or name - local listFrame = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL]; - for i=1, listFrame.numButtons do - local button = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL.."Button"..i]; - -- See if checked or not - if ( L_UIDropDownMenu_GetSelectedName(frame) ) then - if ( button:GetText() == L_UIDropDownMenu_GetSelectedName(frame) ) then - return i; - end - elseif ( L_UIDropDownMenu_GetSelectedValue(frame) ) then - if ( button.value == L_UIDropDownMenu_GetSelectedValue(frame) ) then - return i; - end - end - end - end -end - -function L_UIDropDownMenu_GetSelectedValue(frame) - return frame.selectedValue; -end - -function L_UIDropDownMenuButton_OnClick(self) - local checked = self.checked; - if ( type (checked) == "function" ) then - checked = checked(self); - end - - - if ( self.keepShownOnClick ) then - if not self.notCheckable then - if ( checked ) then - _G[self:GetName().."Check"]:Hide(); - _G[self:GetName().."UnCheck"]:Show(); - checked = false; - else - _G[self:GetName().."Check"]:Show(); - _G[self:GetName().."UnCheck"]:Hide(); - checked = true; - end - end - else - self:GetParent():Hide(); - end - - if ( type (self.checked) ~= "function" ) then - self.checked = checked; - end - - -- saving this here because func might use a dropdown, changing this self's attributes - local playSound = true; - if ( self.noClickSound ) then - playSound = false; - end - - local func = self.func; - if ( func ) then - func(self, self.arg1, self.arg2, checked); - else - return; - end - - if ( playSound ) then - PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON); - end -end - -function L_HideDropDownMenu(level) - local listFrame = _G["L_DropDownList"..level]; - listFrame:Hide(); -end - -function L_ToggleDropDownMenu(level, value, dropDownFrame, anchorName, xOffset, yOffset, menuList, button, autoHideDelay) - if ( not level ) then - level = 1; - end - L_UIDropDownMenuDelegate:SetAttribute("createframes-level", level); - L_UIDropDownMenuDelegate:SetAttribute("createframes-index", 0); - L_UIDropDownMenuDelegate:SetAttribute("createframes", true); - L_UIDROPDOWNMENU_MENU_LEVEL = level; - L_UIDROPDOWNMENU_MENU_VALUE = value; - local listFrameName = "L_DropDownList"..level; - local listFrame = _G[listFrameName]; - local tempFrame; - local point, relativePoint, relativeTo; - if ( not dropDownFrame ) then - tempFrame = button:GetParent(); - else - tempFrame = dropDownFrame; - end - if ( listFrame:IsShown() and (L_UIDROPDOWNMENU_OPEN_MENU == tempFrame) ) then - listFrame:Hide(); - else - -- Set the dropdownframe scale - local uiScale; - local uiParentScale = UIParent:GetScale(); - if ( GetCVar("useUIScale") == "1" ) then - uiScale = tonumber(GetCVar("uiscale")); - if ( uiParentScale < uiScale ) then - uiScale = uiParentScale; - end - else - uiScale = uiParentScale; - end - listFrame:SetScale(uiScale); - - -- Hide the listframe anyways since it is redrawn OnShow() - listFrame:Hide(); - - -- Frame to anchor the dropdown menu to - local anchorFrame; - - -- Display stuff - -- Level specific stuff - if ( level == 1 ) then - L_UIDropDownMenuDelegate:SetAttribute("openmenu", dropDownFrame); - listFrame:ClearAllPoints(); - -- If there's no specified anchorName then use left side of the dropdown menu - if ( not anchorName ) then - -- See if the anchor was set manually using setanchor - if ( dropDownFrame.xOffset ) then - xOffset = dropDownFrame.xOffset; - end - if ( dropDownFrame.yOffset ) then - yOffset = dropDownFrame.yOffset; - end - if ( dropDownFrame.point ) then - point = dropDownFrame.point; - end - if ( dropDownFrame.relativeTo ) then - relativeTo = dropDownFrame.relativeTo; - else - relativeTo = GetChild(L_UIDROPDOWNMENU_OPEN_MENU, L_UIDROPDOWNMENU_OPEN_MENU:GetName(), "Left"); - end - if ( dropDownFrame.relativePoint ) then - relativePoint = dropDownFrame.relativePoint; - end - elseif ( anchorName == "cursor" ) then - relativeTo = nil; - local cursorX, cursorY = GetCursorPosition(); - cursorX = cursorX/uiScale; - cursorY = cursorY/uiScale; - - if ( not xOffset ) then - xOffset = 0; - end - if ( not yOffset ) then - yOffset = 0; - end - xOffset = cursorX + xOffset; - yOffset = cursorY + yOffset; - else - -- See if the anchor was set manually using setanchor - if ( dropDownFrame.xOffset ) then - xOffset = dropDownFrame.xOffset; - end - if ( dropDownFrame.yOffset ) then - yOffset = dropDownFrame.yOffset; - end - if ( dropDownFrame.point ) then - point = dropDownFrame.point; - end - if ( dropDownFrame.relativeTo ) then - relativeTo = dropDownFrame.relativeTo; - else - relativeTo = anchorName; - end - if ( dropDownFrame.relativePoint ) then - relativePoint = dropDownFrame.relativePoint; - end - end - if ( not xOffset or not yOffset ) then - xOffset = 8; - yOffset = 22; - end - if ( not point ) then - point = "TOPLEFT"; - end - if ( not relativePoint ) then - relativePoint = "BOTTOMLEFT"; - end - listFrame:SetPoint(point, relativeTo, relativePoint, xOffset, yOffset); - else - if ( not dropDownFrame ) then - dropDownFrame = L_UIDROPDOWNMENU_OPEN_MENU; - end - listFrame:ClearAllPoints(); - -- If this is a dropdown button, not the arrow anchor it to itself - if ( strsub(button:GetParent():GetName(), 0,14) == "L_DropDownList" and strlen(button:GetParent():GetName()) == 15 ) then - anchorFrame = button; - else - anchorFrame = button:GetParent(); - end - point = "TOPLEFT"; - relativePoint = "TOPRIGHT"; - listFrame:SetPoint(point, anchorFrame, relativePoint, 0, 0); - end - - -- Change list box appearance depending on display mode - if ( dropDownFrame and dropDownFrame.displayMode == "MENU" ) then - _G[listFrameName.."Backdrop"]:Hide(); - _G[listFrameName.."MenuBackdrop"]:Show(); - else - _G[listFrameName.."Backdrop"]:Show(); - _G[listFrameName.."MenuBackdrop"]:Hide(); - end - dropDownFrame.menuList = menuList; - L_UIDropDownMenu_Initialize(dropDownFrame, dropDownFrame.initialize, nil, level, menuList); - -- If no items in the drop down don't show it - if ( listFrame.numButtons == 0 ) then - return; - end - - -- Check to see if the dropdownlist is off the screen, if it is anchor it to the top of the dropdown button - listFrame:Show(); - -- Hack since GetCenter() is returning coords relative to 1024x768 - local x, y = listFrame:GetCenter(); - -- Hack will fix this in next revision of dropdowns - if ( not x or not y ) then - listFrame:Hide(); - return; - end - - listFrame.onHide = dropDownFrame.onHide; - - - -- We just move level 1 enough to keep it on the screen. We don't necessarily change the anchors. - if ( level == 1 ) then - local offLeft = listFrame:GetLeft()/uiScale; - local offRight = (GetScreenWidth() - listFrame:GetRight())/uiScale; - local offTop = (GetScreenHeight() - listFrame:GetTop())/uiScale; - local offBottom = listFrame:GetBottom()/uiScale; - - local xAddOffset, yAddOffset = 0, 0; - if ( offLeft < 0 ) then - xAddOffset = -offLeft; - elseif ( offRight < 0 ) then - xAddOffset = offRight; - end - - if ( offTop < 0 ) then - yAddOffset = offTop; - elseif ( offBottom < 0 ) then - yAddOffset = -offBottom; - end - - listFrame:ClearAllPoints(); - if ( anchorName == "cursor" ) then - listFrame:SetPoint(point, relativeTo, relativePoint, xOffset + xAddOffset, yOffset + yAddOffset); - else - listFrame:SetPoint(point, relativeTo, relativePoint, xOffset + xAddOffset, yOffset + yAddOffset); - end - else - -- Determine whether the menu is off the screen or not - local offscreenY, offscreenX; - if ( (y - listFrame:GetHeight()/2) < 0 ) then - offscreenY = 1; - end - if ( listFrame:GetRight() > GetScreenWidth() ) then - offscreenX = 1; - end - if ( offscreenY and offscreenX ) then - point = gsub(point, "TOP(.*)", "BOTTOM%1"); - point = gsub(point, "(.*)LEFT", "%1RIGHT"); - relativePoint = gsub(relativePoint, "TOP(.*)", "BOTTOM%1"); - relativePoint = gsub(relativePoint, "(.*)RIGHT", "%1LEFT"); - xOffset = -11; - yOffset = -14; - elseif ( offscreenY ) then - point = gsub(point, "TOP(.*)", "BOTTOM%1"); - relativePoint = gsub(relativePoint, "TOP(.*)", "BOTTOM%1"); - xOffset = 0; - yOffset = -14; - elseif ( offscreenX ) then - point = gsub(point, "(.*)LEFT", "%1RIGHT"); - relativePoint = gsub(relativePoint, "(.*)RIGHT", "%1LEFT"); - xOffset = -11; - yOffset = 14; - else - xOffset = 0; - yOffset = 14; - end - - listFrame:ClearAllPoints(); - listFrame.parentLevel = tonumber(strmatch(anchorFrame:GetName(), "L_DropDownList(%d+)")); - listFrame.parentID = anchorFrame:GetID(); - listFrame:SetPoint(point, anchorFrame, relativePoint, xOffset, yOffset); - end - - if ( autoHideDelay and tonumber(autoHideDelay)) then - listFrame.showTimer = autoHideDelay; - listFrame.isCounting = 1; - end - end -end - -function L_CloseDropDownMenus(level) - if ( not level ) then - level = 1; - end - for i=level, L_UIDROPDOWNMENU_MAXLEVELS do - _G["L_DropDownList"..i]:Hide(); - end -end - -function L_UIDropDownMenu_OnHide(self) - local id = self:GetID() - if ( self.onHide ) then - self.onHide(id+1); - self.onHide = nil; - end - L_CloseDropDownMenus(id+1); - L_OPEN_DROPDOWNMENUS[id] = nil; - if (id == 1) then - L_UIDROPDOWNMENU_OPEN_MENU = nil; - end - - if self.customFrames then - for index, frame in ipairs(self.customFrames) do - frame:Hide(); - end - - self.customFrames = nil; - end -end - -function L_UIDropDownMenu_SetWidth(frame, width, padding) - local frameName = frame:GetName(); - GetChild(frame, frameName, "Middle"):SetWidth(width); - local defaultPadding = 25; - if ( padding ) then - frame:SetWidth(width + padding); - else - frame:SetWidth(width + defaultPadding + defaultPadding); - end - if ( padding ) then - GetChild(frame, frameName, "Text"):SetWidth(width); - else - GetChild(frame, frameName, "Text"):SetWidth(width - defaultPadding); - end - frame.noResize = 1; -end - -function L_UIDropDownMenu_SetButtonWidth(frame, width) - local frameName = frame:GetName(); - if ( width == "TEXT" ) then - width = GetChild(frame, frameName, "Text"):GetWidth(); - end - - GetChild(frame, frameName, "Button"):SetWidth(width); - frame.noResize = 1; -end - -function L_UIDropDownMenu_SetText(frame, text) - local frameName = frame:GetName(); - GetChild(frame, frameName, "Text"):SetText(text); -end - -function L_UIDropDownMenu_GetText(frame) - local frameName = frame:GetName(); - return GetChild(frame, frameName, "Text"):GetText(); -end - -function L_UIDropDownMenu_ClearAll(frame) - -- Previous code refreshed the menu quite often and was a performance bottleneck - frame.selectedID = nil; - frame.selectedName = nil; - frame.selectedValue = nil; - L_UIDropDownMenu_SetText(frame, ""); - - local button, checkImage, uncheckImage; - for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do - button = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL.."Button"..i]; - button:UnlockHighlight(); - - checkImage = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL.."Button"..i.."Check"]; - checkImage:Hide(); - uncheckImage = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL.."Button"..i.."UnCheck"]; - uncheckImage:Hide(); - end -end - -function L_UIDropDownMenu_JustifyText(frame, justification) - local frameName = frame:GetName(); - local text = GetChild(frame, frameName, "Text"); - text:ClearAllPoints(); - if ( justification == "LEFT" ) then - text:SetPoint("LEFT", GetChild(frame, frameName, "Left"), "LEFT", 27, 2); - text:SetJustifyH("LEFT"); - elseif ( justification == "RIGHT" ) then - text:SetPoint("RIGHT", GetChild(frame, frameName, "Right"), "RIGHT", -43, 2); - text:SetJustifyH("RIGHT"); - elseif ( justification == "CENTER" ) then - text:SetPoint("CENTER", GetChild(frame, frameName, "Middle"), "CENTER", -5, 2); - text:SetJustifyH("CENTER"); - end -end - -function L_UIDropDownMenu_SetAnchor(dropdown, xOffset, yOffset, point, relativeTo, relativePoint) - dropdown.xOffset = xOffset; - dropdown.yOffset = yOffset; - dropdown.point = point; - dropdown.relativeTo = relativeTo; - dropdown.relativePoint = relativePoint; -end - -function L_UIDropDownMenu_GetCurrentDropDown() - if ( L_UIDROPDOWNMENU_OPEN_MENU ) then - return L_UIDROPDOWNMENU_OPEN_MENU; - elseif ( L_UIDROPDOWNMENU_INIT_MENU ) then - return L_UIDROPDOWNMENU_INIT_MENU; - end -end - -function L_UIDropDownMenuButton_GetChecked(self) - return _G[self:GetName().."Check"]:IsShown(); -end - -function L_UIDropDownMenuButton_GetName(self) - return _G[self:GetName().."NormalText"]:GetText(); -end - -function L_UIDropDownMenuButton_OpenColorPicker(self, button) - securecall("CloseMenus"); - if ( not button ) then - button = self; - end - L_UIDROPDOWNMENU_MENU_VALUE = button.value; - L_OpenColorPicker(button); -end - -function L_UIDropDownMenu_DisableButton(level, id) - _G["L_DropDownList"..level.."Button"..id]:Disable(); -end - -function L_UIDropDownMenu_EnableButton(level, id) - _G["L_DropDownList"..level.."Button"..id]:Enable(); -end - -function L_UIDropDownMenu_SetButtonText(level, id, text, colorCode) - local button = _G["L_DropDownList"..level.."Button"..id]; - if ( colorCode) then - button:SetText(colorCode..text.."|r"); - else - button:SetText(text); - end -end - -function L_UIDropDownMenu_SetButtonNotClickable(level, id) - _G["L_DropDownList"..level.."Button"..id]:SetDisabledFontObject(GameFontHighlightSmallLeft); -end - -function L_UIDropDownMenu_SetButtonClickable(level, id) - _G["L_DropDownList"..level.."Button"..id]:SetDisabledFontObject(GameFontDisableSmallLeft); -end - -function L_UIDropDownMenu_DisableDropDown(dropDown) - local dropDownName = dropDown:GetName(); - local label = GetChild(dropDown, dropDownName, "Label"); - if ( label ) then - label:SetVertexColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b); - end - GetChild(dropDown, dropDownName, "Text"):SetVertexColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b); - GetChild(dropDown, dropDownName, "Button"):Disable(); - dropDown.isDisabled = 1; -end - -function L_UIDropDownMenu_EnableDropDown(dropDown) - local dropDownName = dropDown:GetName(); - local label = GetChild(dropDown, dropDownName, "Label"); - if ( label ) then - label:SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b); - end - GetChild(dropDown, dropDownName, "Text"):SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b); - GetChild(dropDown, dropDownName, "Button"):Enable(); - dropDown.isDisabled = nil; -end - -function L_UIDropDownMenu_IsEnabled(dropDown) - return not dropDown.isDisabled; -end - -function L_UIDropDownMenu_GetValue(id) - --Only works if the dropdown has just been initialized, lame, I know =( - local button = _G["L_DropDownList1Button"..id]; - if ( button ) then - return _G["L_DropDownList1Button"..id].value; - else - return nil; - end -end - -function L_OpenColorPicker(info) - ColorPickerFrame.func = info.swatchFunc; - ColorPickerFrame.hasOpacity = info.hasOpacity; - ColorPickerFrame.opacityFunc = info.opacityFunc; - ColorPickerFrame.opacity = info.opacity; - ColorPickerFrame.previousValues = {r = info.r, g = info.g, b = info.b, opacity = info.opacity}; - ColorPickerFrame.cancelFunc = info.cancelFunc; - ColorPickerFrame.extraInfo = info.extraInfo; - -- This must come last, since it triggers a call to ColorPickerFrame.func() - ColorPickerFrame:SetColorRGB(info.r, info.g, info.b); - ShowUIPanel(ColorPickerFrame); -end - -function L_ColorPicker_GetPreviousValues() - return ColorPickerFrame.previousValues.r, ColorPickerFrame.previousValues.g, ColorPickerFrame.previousValues.b; -end +-- $Id: LibUIDropDownMenu.lua 73 2021-05-18 17:04:17Z arithmandar $ +-- ---------------------------------------------------------------------------- +-- Localized Lua globals. +-- ---------------------------------------------------------------------------- +local _G = getfenv(0) +local tonumber, type, string, table = _G.tonumber, _G.type, _G.string, _G.table +local tinsert = table.insert +local strsub, strlen, strmatch, gsub = _G.strsub, _G.strlen, _G.strmatch, _G.gsub +local max, match = _G.max, _G.match +local securecall, issecure = _G.securecall, _G.issecure +local wipe = table.wipe +-- WoW +local CreateFrame, GetCursorPosition, GetCVar, GetScreenHeight, GetScreenWidth, PlaySound = _G.CreateFrame, _G.GetCursorPosition, _G.GetCVar, _G.GetScreenHeight, _G.GetScreenWidth, _G.PlaySound +local GetBuildInfo = _G.GetBuildInfo +local GameTooltip, GetAppropriateTooltip, tooltip, GetValueOrCallFunction +local CloseMenus, ShowUIPanel = _G.CloseMenus, _G.ShowUIPanel +local GameTooltip_SetTitle, GameTooltip_AddInstructionLine, GameTooltip_AddNormalLine, GameTooltip_AddColoredLine = _G.GameTooltip_SetTitle, _G.GameTooltip_AddInstructionLine, _G.GameTooltip_AddNormalLine, _G.GameTooltip_AddColoredLine + +-- ---------------------------------------------------------------------------- +local MAJOR_VERSION = "LibUIDropDownMenu-4.0" +local MINOR_VERSION = 90000 + tonumber(("$Rev: 73 $"):match("%d+")) + + +local LibStub = _G.LibStub +if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end +local lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) +if not lib then return end + +-- Determine WoW TOC Version +local WoWClassic, WoWRetail +local wowtocversion = select(4, GetBuildInfo()) +if wowtocversion < 30000 then + WoWClassic = true +else + WoWRetail = true +end + +if WoWClassic then + GameTooltip = _G.GameTooltip + tooltip = GameTooltip +else -- Shadowlands + GetAppropriateTooltip = _G.GetAppropriateTooltip + tooltip = GetAppropriateTooltip() + GetValueOrCallFunction = _G.GetValueOrCallFunction +end + +-- ////////////////////////////////////////////////////////////// +L_UIDROPDOWNMENU_MAXBUTTONS = 1; +L_UIDROPDOWNMENU_MAXLEVELS = 2; +L_UIDROPDOWNMENU_BUTTON_HEIGHT = 16; +L_UIDROPDOWNMENU_BORDER_HEIGHT = 15; +-- The current open menu +L_UIDROPDOWNMENU_OPEN_MENU = nil; +-- The current menu being initialized +L_UIDROPDOWNMENU_INIT_MENU = nil; +-- Current level shown of the open menu +L_UIDROPDOWNMENU_MENU_LEVEL = 1; +-- Current value of the open menu +L_UIDROPDOWNMENU_MENU_VALUE = nil; +-- Time to wait to hide the menu +L_UIDROPDOWNMENU_SHOW_TIME = 2; +-- Default dropdown text height +L_UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT = nil; +-- List of open menus +L_OPEN_DROPDOWNMENUS = {}; + +local L_DropDownList1, L_DropDownList2 + +local delegateFrame = CreateFrame("FRAME"); +delegateFrame:SetScript("OnAttributeChanged", function(self, attribute, value) + if ( attribute == "createframes" and value == true ) then + lib:UIDropDownMenu_CreateFrames(self:GetAttribute("createframes-level"), self:GetAttribute("createframes-index")); + elseif ( attribute == "initmenu" ) then + L_UIDROPDOWNMENU_INIT_MENU = value; + elseif ( attribute == "openmenu" ) then + L_UIDROPDOWNMENU_OPEN_MENU = value; + end +end); + +function lib:UIDropDownMenu_InitializeHelper(frame) + -- This deals with the potentially tainted stuff! + if ( frame ~= L_UIDROPDOWNMENU_OPEN_MENU ) then + L_UIDROPDOWNMENU_MENU_LEVEL = 1; + end + + -- Set the frame that's being intialized + delegateFrame:SetAttribute("initmenu", frame); + + -- Hide all the buttons + local button, dropDownList; + for i = 1, L_UIDROPDOWNMENU_MAXLEVELS, 1 do + dropDownList = _G["L_DropDownList"..i]; + if ( i >= L_UIDROPDOWNMENU_MENU_LEVEL or frame ~= L_UIDROPDOWNMENU_OPEN_MENU ) then + dropDownList.numButtons = 0; + dropDownList.maxWidth = 0; + for j=1, L_UIDROPDOWNMENU_MAXBUTTONS, 1 do + button = _G["L_DropDownList"..i.."Button"..j]; + button:Hide(); + end + dropDownList:Hide(); + end + end + frame:SetHeight(L_UIDROPDOWNMENU_BUTTON_HEIGHT * 2); +end +-- ////////////////////////////////////////////////////////////// +-- L_UIDropDownMenuButtonTemplate +local function create_MenuButton(name, parent) + local f = CreateFrame("Button", name, parent or nil) + f:SetWidth(100) + f:SetHeight(16) + f:SetFrameLevel(f:GetParent():GetFrameLevel()+2) + + f.Highlight = f:CreateTexture(name.."Highlight", "BACKGROUND") + f.Highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") + f.Highlight:SetBlendMode("ADD") + f.Highlight:SetAllPoints() + f.Highlight:Hide() + + f.Check = f:CreateTexture(name.."Check", "ARTWORK") + f.Check:SetTexture("Interface\\Common\\UI-DropDownRadioChecks") + f.Check:SetSize(16, 16) + f.Check:SetPoint("LEFT", f, 0, 0) + f.Check:SetTexCoord(0, 0.5, 0.5, 1) + + f.UnCheck = f:CreateTexture(name.."UnCheck", "ARTWORK") + f.UnCheck:SetTexture("Interface\\Common\\UI-DropDownRadioChecks") + f.UnCheck:SetSize(16, 16) + f.UnCheck:SetPoint("LEFT", f, 0, 0) + f.UnCheck:SetTexCoord(0.5, 1, 0.5, 1) + + f.Icon = f:CreateTexture(name.."Icon", "ARTWORK") + f.Icon:SetSize(16, 16) + f.Icon:SetPoint("RIGHT", f, 0, 0) + f.Icon:Hide() + + -- ColorSwatch + local fcw + if WoWClassic then + fcw = CreateFrame("Button", name.."ColorSwatch", f) + else + fcw = CreateFrame("Button", name.."ColorSwatch", f, BackdropTemplateMixin and "ColorSwatchTemplate" or nil) + end + fcw:SetPoint("RIGHT", f, -6, 0) + fcw:Hide() + if WoWClassic then + fcw:SetSize(16, 16) + fcw.SwatchBg = fcw:CreateTexture(name.."ColorSwatchSwatchBg", "BACKGROUND") + fcw.SwatchBg:SetVertexColor(1, 1, 1) + fcw.SwatchBg:SetWidth(14) + fcw.SwatchBg:SetHeight(14) + fcw.SwatchBg:SetPoint("CENTER", fcw, 0, 0) + local button1NormalTexture = fcw:CreateTexture(name.."ColorSwatchNormalTexture") + button1NormalTexture:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch") + button1NormalTexture:SetAllPoints() + fcw:SetNormalTexture(button1NormalTexture) + end + fcw:SetScript("OnClick", function(self, button, down) + CloseMenus() + lib:UIDropDownMenuButton_OpenColorPicker(self:GetParent()) + end) + fcw:SetScript("OnEnter", function(self, motion) + lib:CloseDropDownMenus(self:GetParent():GetParent():GetID() + 1) + _G[self:GetName().."SwatchBg"]:SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) + end) + fcw:SetScript("OnLeave", function(self, motion) + _G[self:GetName().."SwatchBg"]:SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b); + end) + f.ColorSwatch = fcw + + -- ExpandArrow + local fea = CreateFrame("Button", name.."ExpandArrow", f) + + fea:SetSize(16, 16) + fea:SetPoint("RIGHT", f, 0, 0) + fea:Hide() + local button2NormalTexture = fea:CreateTexture(name.."ExpandArrowNormalTexture") + button2NormalTexture:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow") + button2NormalTexture:SetAllPoints() + fea:SetNormalTexture(button2NormalTexture) + fea:SetScript("OnMouseDown", function(self, button) + if self:IsEnabled() then + lib:ToggleDropDownMenu(self:GetParent():GetParent():GetID() + 1, self:GetParent().value, nil, nil, nil, nil, self:GetParent().menuList, self); + PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON); + end + end) + fea:SetScript("OnEnter", function(self, motion) + local level = self:GetParent():GetParent():GetID() + 1 + lib:CloseDropDownMenus(level) + if self:IsEnabled() then + local listFrame = _G["L_DropDownList"..level]; + if ( not listFrame or not listFrame:IsShown() or select(2, listFrame:GetPoint()) ~= self ) then + lib:ToggleDropDownMenu(level, self:GetParent().value, nil, nil, nil, nil, self:GetParent().menuList, self) + end + end + end) + f.ExpandArrow = fea + + -- InvisibleButton + local fib = CreateFrame("Button", name.."InvisibleButton", f) + fib:Hide() + fib:SetPoint("TOPLEFT", f, 0, 0) + fib:SetPoint("BOTTOMLEFT", f, 0, 0) + fib:SetPoint("RIGHT", fcw, "LEFT", 0, 0) + fib:SetScript("OnEnter", function(self, motion) + lib:CloseDropDownMenus(self:GetParent():GetParent():GetID() + 1); + local parent = self:GetParent(); + if ( parent.tooltipTitle and parent.tooltipWhileDisabled) then + if ( parent.tooltipOnButton ) then + tooltip:SetOwner(parent, "ANCHOR_RIGHT"); + GameTooltip_SetTitle(tooltip, parent.tooltipTitle); + if parent.tooltipInstruction then + GameTooltip_AddInstructionLine(tooltip, parent.tooltipInstruction); + end + if parent.tooltipText then + GameTooltip_AddNormalLine(tooltip, parent.tooltipText, true); + end + if parent.tooltipWarning then + GameTooltip_AddColoredLine(tooltip, parent.tooltipWarning, RED_FONT_COLOR, true); + end + tooltip:Show(); + end + end + end) + fib:SetScript("OnLeave", function(self, motion) + tooltip:Hide(); + end) + f.invisibleButton = fib + + -- UIDropDownMenuButton Scripts + local function button_OnEnter(self) + if ( self.hasArrow ) then + local level = self:GetParent():GetID() + 1; + local listFrame = _G["L_DropDownList"..level]; + if ( not listFrame or not listFrame:IsShown() or select(2, listFrame:GetPoint()) ~= self ) then + lib:ToggleDropDownMenu(self:GetParent():GetID() + 1, self.value, nil, nil, nil, nil, self.menuList, self); + end + else + lib:CloseDropDownMenus(self:GetParent():GetID() + 1); + end + self.Highlight:Show(); + if ( self.tooltipTitle and not self.noTooltipWhileEnabled ) then + if ( self.tooltipOnButton ) then + tooltip:SetOwner(self, "ANCHOR_RIGHT"); + GameTooltip_SetTitle(tooltip, self.tooltipTitle); + if self.tooltipText then + GameTooltip_AddNormalLine(tooltip, self.tooltipText, true); + end + tooltip:Show(); + end + end + + if ( self.mouseOverIcon ~= nil ) then + self.Icon:SetTexture(self.mouseOverIcon); + self.Icon:Show(); + end + if WoWRetail then + GetValueOrCallFunction(self, "funcOnEnter", self); + end + end + + local function button_OnLeave(self) + self.Highlight:Hide(); + tooltip:Hide(); + + if ( self.mouseOverIcon ~= nil ) then + if ( self.icon ~= nil ) then + self.Icon:SetTexture(self.icon); + else + self.Icon:Hide(); + end + end + + if WoWRetail then + GetValueOrCallFunction(self, "funcOnLeave", self); + end + end + + local function button_OnClick(self) + local checked = self.checked; + if ( type (checked) == "function" ) then + checked = checked(self); + end + + if ( self.keepShownOnClick ) then + if not self.notCheckable then + if ( checked ) then + _G[self:GetName().."Check"]:Hide(); + _G[self:GetName().."UnCheck"]:Show(); + checked = false; + else + _G[self:GetName().."Check"]:Show(); + _G[self:GetName().."UnCheck"]:Hide(); + checked = true; + end + end + else + self:GetParent():Hide(); + end + + if ( type (self.checked) ~= "function" ) then + self.checked = checked; + end + + -- saving this here because func might use a dropdown, changing this self's attributes + local playSound = true; + if ( self.noClickSound ) then + playSound = false; + end + + local func = self.func; + if ( func ) then + func(self, self.arg1, self.arg2, checked); + else + return; + end + + if ( playSound ) then + PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON); + end + end + + f:SetScript("OnClick", function(self, button, down) + button_OnClick(self, button, down) + end) + f:SetScript("OnEnter", function(self, motion) + button_OnEnter(self) + end) + f:SetScript("OnLeave", function(self, motion) + button_OnLeave(self) + end) + f:SetScript("OnEnable", function(self) + self.invisibleButton:Hide() + end) + f:SetScript("OnDisable", function(self) + self.invisibleButton:Show() + end) + + local text1 = f:CreateFontString(name.."NormalText") + f:SetFontString(text1) + text1:SetPoint("LEFT", f, -5, 0) + f:SetNormalFontObject("GameFontHighlightSmallLeft") + f:SetHighlightFontObject("GameFontHighlightSmallLeft") + f:SetDisabledFontObject("GameFontDisableSmallLeft") + + return f +end + +-- ////////////////////////////////////////////////////////////// +-- L_UIDropDownListTemplate +local BACKDROP_DIALOG_DARK = { + bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark", + edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", + tile = true, + tileSize = 32, + edgeSize = 32, + insets = { left = 11, right = 12, top = 12, bottom = 9, }, +} +local function creatre_DropDownList(name, parent) + local f = _G[name] or CreateFrame("Button", name) + f:SetParent(parent or nil) + f:Hide() + f:SetFrameStrata("DIALOG") + f:EnableMouse(true) + + --local fbd = _G[name.."Border"] or CreateFrame("Frame", name.."Border", f, BackdropTemplateMixin and "DialogBorderDarkTemplate" or nil) + local fbd = _G[name.."Border"] or CreateFrame("Frame", name.."Border", f, BackdropTemplateMixin and "BackdropTemplate" or nil) + fbd:SetAllPoints() + fbd:SetBackdrop(BACKDROP_DIALOG_DARK) + f.Border = fbd + + --local fmb = _G[name.."MenuBackdrop"] or CreateFrame("Frame", name.."MenuBackdrop", f, BackdropTemplateMixin and "TooltipBackdropTemplate" or nil) + local fmb = _G[name.."MenuBackdrop"] or CreateFrame("Frame", name.."MenuBackdrop", f, BackdropTemplateMixin and "BackdropTemplate" or nil) + fmb:SetAllPoints() + fmb:SetBackdrop(BACKDROP_TOOLTIP_16_16_5555) + fmb:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) + fmb:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) + f.MenuBackdrop = fmb + + f.Button1 = _G[name.."Button1"] or create_MenuButton(name.."Button1", f) + f.Button1:SetID(1) + + f:SetScript("OnClick", function(self) + self:Hide() + end) + -- If dropdown is visible then see if its timer has expired, if so hide the frame + f:SetScript("OnUpdate", function(self, elapsed) + if ( self.shouldRefresh ) then + lib:UIDropDownMenu_RefreshDropDownSize(self); + self.shouldRefresh = false; + end + end) + f:SetScript("OnShow", function(self) + if ( self.onShow ) then + self.onShow(); + self.onShow = nil; + end + + for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do + if (not self.noResize) then + _G[self:GetName().."Button"..i]:SetWidth(self.maxWidth); + end + end + + if (not self.noResize) then + self:SetWidth(self.maxWidth+25); + end + + if ( self:GetID() > 1 ) then + self.parent = _G["L_DropDownList"..(self:GetID() - 1)]; + end + end) + f:SetScript("OnHide", function(self) + local id = self:GetID() + if ( self.onHide ) then + self.onHide(id+1); + self.onHide = nil; + end + lib:CloseDropDownMenus(id+1); + L_OPEN_DROPDOWNMENUS[id] = nil; + if (id == 1) then + L_UIDROPDOWNMENU_OPEN_MENU = nil; + end + + if self.customFrames then + for index, frame in ipairs(self.customFrames) do + frame:Hide(); + end + + self.customFrames = nil; + end + end) + + return f +end + +-- ////////////////////////////////////////////////////////////// +-- L_UIDropDownMenuTemplate +local function create_DropDownMenu(name, parent) + local f + if type(name) == "table" then + f = name + name = f:GetName() + else + f = CreateFrame("Frame", name, parent or nil) + end + f:SetSize(40, 32) + + f.Left = f:CreateTexture(name.."Left", "ARTWORK") + f.Left:SetTexture("Interface\\Glues\\CharacterCreate\\CharacterCreate-LabelFrame") + f.Left:SetSize(25, 64) + f.Left:SetPoint("TOPLEFT", f, 0, 17) + f.Left:SetTexCoord(0, 0.1953125, 0, 1) + + f.Middle = f:CreateTexture(name.."Middle", "ARTWORK") + f.Middle:SetTexture("Interface\\Glues\\CharacterCreate\\CharacterCreate-LabelFrame") + f.Middle:SetSize(115, 64) + f.Middle:SetPoint("LEFT", f.Left, "RIGHT") + f.Middle:SetTexCoord(0.1953125, 0.8046875, 0, 1) + + f.Right = f:CreateTexture(name.."Right", "ARTWORK") + f.Right:SetTexture("Interface\\Glues\\CharacterCreate\\CharacterCreate-LabelFrame") + f.Right:SetSize(25, 64) + f.Right:SetPoint("LEFT", f.Middle, "RIGHT") + f.Right:SetTexCoord(0.8046875, 1, 0, 1) + + f.Text = f:CreateFontString(name.."Text", "ARTWORK", "GameFontHighlightSmall") + f.Text:SetWordWrap(false) + f.Text:SetJustifyH("RIGHT") + f.Text:SetSize(0, 10) + f.Text:SetPoint("RIGHT", f.Right, -43, 2) + + f.Icon = f:CreateTexture(name.."Icon", "OVERLAY") + f.Icon:Hide() + f.Icon:SetSize(16, 16) + f.Icon:SetPoint("LEFT", 30, 2) + + f.Button = CreateFrame("Button", name.."Button", f) + f.Button:SetMotionScriptsWhileDisabled(true) + f.Button:SetSize(24, 24) + f.Button:SetPoint("TOPRIGHT", f.Right, -16, -18) + + f.Button.NormalTexture = f.Button:CreateTexture(name.."NormalTexture") + f.Button.NormalTexture:SetTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Up") + f.Button.NormalTexture:SetSize(24, 24) + f.Button.NormalTexture:SetPoint("RIGHT", f.Button, 0, 0) + f.Button:SetNormalTexture(f.Button.NormalTexture) + + f.Button.PushedTexture = f.Button:CreateTexture(name.."PushedTexture") + f.Button.PushedTexture:SetTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Down") + f.Button.PushedTexture:SetSize(24, 24) + f.Button.PushedTexture:SetPoint("RIGHT", f.Button, 0, 0) + f.Button:SetPushedTexture(f.Button.PushedTexture) + + f.Button.DisabledTexture = f.Button:CreateTexture(name.."DisabledTexture") + f.Button.DisabledTexture:SetTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Disabled") + f.Button.DisabledTexture:SetSize(24, 24) + f.Button.DisabledTexture:SetPoint("RIGHT", f.Button, 0, 0) + f.Button:SetDisabledTexture(f.Button.DisabledTexture) + + f.Button.HighlightTexture = f.Button:CreateTexture(name.."HighlightTexture") + f.Button.HighlightTexture:SetTexture("Interface\\Buttons\\UI-Common-MouseHilight") + f.Button.HighlightTexture:SetSize(24, 24) + f.Button.HighlightTexture:SetPoint("RIGHT", f.Button, 0, 0) + f.Button.HighlightTexture:SetBlendMode("ADD") + f.Button:SetHighlightTexture(f.Button.HighlightTexture) + + -- Button Script + f.Button:SetScript("OnEnter", function(self, motion) + local parent = self:GetParent() + local myscript = parent:GetScript("OnEnter") + if(myscript ~= nil) then + myscript(parent) + end + end) + f.Button:SetScript("OnLeave", function(self, motion) + local parent = self:GetParent() + local myscript = parent:GetScript("OnLeave") + if(myscript ~= nil) then + myscript(parent) + end + end) + f.Button:SetScript("OnMouseDown", function(self, button) + if self:IsEnabled() then + local parent = self:GetParent() + lib:ToggleDropDownMenu(nil, nil, parent) + PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON) + end + end) + + -- UIDropDownMenu Script + f:SetScript("OnHide", function(self) + lib:CloseDropDownMenus() + end) + + return f +end +-- End of frame templates +-- ////////////////////////////////////////////////////////////// + +-- ////////////////////////////////////////////////////////////// +-- Handling two frames from LibUIDropDownMenu.xml +local function create_DropDownButtons() + L_DropDownList1 = creatre_DropDownList("L_DropDownList1") + L_DropDownList1:SetToplevel(true) + L_DropDownList1:SetFrameStrata("FULLSCREEN_DIALOG") + L_DropDownList1:Hide() + L_DropDownList1:SetID(1) + L_DropDownList1:SetSize(180, 10) + local _, fontHeight, _ = _G["L_DropDownList1Button1NormalText"]:GetFont() + L_UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT = fontHeight + + L_DropDownList2 = creatre_DropDownList("L_DropDownList2") + L_DropDownList2:SetToplevel(true) + L_DropDownList2:SetFrameStrata("FULLSCREEN_DIALOG") + L_DropDownList2:Hide() + L_DropDownList2:SetID(2) + L_DropDownList2:SetSize(180, 10) + + -- UIParent integration; since we customize the name of DropDownList, we need to add it to golbal UIMenus table. + --tinsert(UIMenus, "L_DropDownList1"); + --tinsert(UIMenus, "L_DropDownList2"); + + -- Alternative by Dahk Celes (DDC) that avoids tainting UIMenus and CloseMenus() + hooksecurefunc("CloseMenus", function() + L_DropDownList1:Hide() + L_DropDownList2:Hide() + end) +end + +do + if lib then + create_DropDownButtons() + end +end + +-- ////////////////////////////////////////////////////////////// +-- Global function to replace L_UIDropDownMenuTemplate +function lib:Create_UIDropDownMenu(name, parent) + return create_DropDownMenu(name, parent) +end + +local function GetChild(frame, name, key) + if (frame[key]) then + return frame[key]; + elseif name then + return _G[name..key]; + end + + return nil; +end + +function lib:UIDropDownMenu_Initialize(frame, initFunction, displayMode, level, menuList) + frame.menuList = menuList; + + --securecall("initializeHelper", frame); + lib:UIDropDownMenu_InitializeHelper(frame) + + -- Set the initialize function and call it. The initFunction populates the dropdown list. + if ( initFunction ) then + lib:UIDropDownMenu_SetInitializeFunction(frame, initFunction); + initFunction(frame, level, frame.menuList); + end + + --master frame + if(level == nil) then + level = 1; + end + + local dropDownList = _G["L_DropDownList"..level]; + dropDownList.dropdown = frame; + dropDownList.shouldRefresh = true; + + lib:UIDropDownMenu_SetDisplayMode(frame, displayMode); +end + +function lib:UIDropDownMenu_SetInitializeFunction(frame, initFunction) + frame.initialize = initFunction; +end + +function lib:UIDropDownMenu_SetDisplayMode(frame, displayMode) + -- Change appearance based on the displayMode + -- Note: this is a one time change based on previous behavior. + if ( displayMode == "MENU" ) then + local name = frame:GetName(); + GetChild(frame, name, "Left"):Hide(); + GetChild(frame, name, "Middle"):Hide(); + GetChild(frame, name, "Right"):Hide(); + local button = GetChild(frame, name, "Button"); + local buttonName = button:GetName(); + GetChild(button, buttonName, "NormalTexture"):SetTexture(nil); + GetChild(button, buttonName, "DisabledTexture"):SetTexture(nil); + GetChild(button, buttonName, "PushedTexture"):SetTexture(nil); + GetChild(button, buttonName, "HighlightTexture"):SetTexture(nil); + local text = GetChild(frame, name, "Text"); + + button:ClearAllPoints(); + button:SetPoint("LEFT", text, "LEFT", -9, 0); + button:SetPoint("RIGHT", text, "RIGHT", 6, 0); + frame.displayMode = "MENU"; + end +end + +function lib:UIDropDownMenu_RefreshDropDownSize(self) + self.maxWidth = lib:UIDropDownMenu_GetMaxButtonWidth(self); + self:SetWidth(self.maxWidth + 25); + + for i=1, L_UIDROPDOWNMENU_MAXBUTTONS, 1 do + local icon = _G[self:GetName().."Button"..i.."Icon"]; + + if ( icon.tFitDropDownSizeX ) then + icon:SetWidth(self.maxWidth - 5); + end + end +end + +--[[ +List of button attributes +====================================================== +info.text = [STRING] -- The text of the button +info.value = [ANYTHING] -- The value that L_UIDROPDOWNMENU_MENU_VALUE is set to when the button is clicked +info.func = [function()] -- The function that is called when you click the button +info.checked = [nil, true, function] -- Check the button if true or function returns true +info.isNotRadio = [nil, true] -- Check the button uses radial image if false check box image if true +info.isTitle = [nil, true] -- If it's a title the button is disabled and the font color is set to yellow +info.disabled = [nil, true] -- Disable the button and show an invisible button that still traps the mouseover event so menu doesn't time out +info.tooltipWhileDisabled = [nil, 1] -- Show the tooltip, even when the button is disabled. +info.hasArrow = [nil, true] -- Show the expand arrow for multilevel menus +info.hasColorSwatch = [nil, true] -- Show color swatch or not, for color selection +info.r = [1 - 255] -- Red color value of the color swatch +info.g = [1 - 255] -- Green color value of the color swatch +info.b = [1 - 255] -- Blue color value of the color swatch +info.colorCode = [STRING] -- "|cAARRGGBB" embedded hex value of the button text color. Only used when button is enabled +info.swatchFunc = [function()] -- Function called by the color picker on color change +info.hasOpacity = [nil, 1] -- Show the opacity slider on the colorpicker frame +info.opacity = [0.0 - 1.0] -- Percentatge of the opacity, 1.0 is fully shown, 0 is transparent +info.opacityFunc = [function()] -- Function called by the opacity slider when you change its value +info.cancelFunc = [function(previousValues)] -- Function called by the colorpicker when you click the cancel button (it takes the previous values as its argument) +info.notClickable = [nil, 1] -- Disable the button and color the font white +info.notCheckable = [nil, 1] -- Shrink the size of the buttons and don't display a check box +info.owner = [Frame] -- Dropdown frame that "owns" the current dropdownlist +info.keepShownOnClick = [nil, 1] -- Don't hide the dropdownlist after a button is clicked +info.tooltipTitle = [nil, STRING] -- Title of the tooltip shown on mouseover +info.tooltipText = [nil, STRING] -- Text of the tooltip shown on mouseover +info.tooltipOnButton = [nil, 1] -- Show the tooltip attached to the button instead of as a Newbie tooltip. +info.justifyH = [nil, "CENTER"] -- Justify button text +info.arg1 = [ANYTHING] -- This is the first argument used by info.func +info.arg2 = [ANYTHING] -- This is the second argument used by info.func +info.fontObject = [FONT] -- font object replacement for Normal and Highlight +info.menuTable = [TABLE] -- This contains an array of info tables to be displayed as a child menu +info.noClickSound = [nil, 1] -- Set to 1 to suppress the sound when clicking the button. The sound only plays if .func is set. +info.padding = [nil, NUMBER] -- Number of pixels to pad the text on the right side +info.leftPadding = [nil, NUMBER] -- Number of pixels to pad the button on the left side +info.minWidth = [nil, NUMBER] -- Minimum width for this line +info.customFrame = frame -- Allows this button to be a completely custom frame, should inherit from UIDropDownCustomMenuEntryTemplate and override appropriate methods. +info.icon = [TEXTURE] -- An icon for the button. +info.mouseOverIcon = [TEXTURE] -- An override icon when a button is moused over. +info.ignoreAsMenuSelection [nil, true] -- Never set the menu text/icon to this, even when this button is checked +]] + +-- Create (return) empty table +function lib:UIDropDownMenu_CreateInfo() + return {}; +end + +function lib:UIDropDownMenu_CreateFrames(level, index) + while ( level > L_UIDROPDOWNMENU_MAXLEVELS ) do + L_UIDROPDOWNMENU_MAXLEVELS = L_UIDROPDOWNMENU_MAXLEVELS + 1; + --local newList = CreateFrame("Button", "L_DropDownList"..L_UIDROPDOWNMENU_MAXLEVELS, nil, "L_UIDropDownListTemplate"); + local newList = creatre_DropDownList("L_DropDownList"..L_UIDROPDOWNMENU_MAXLEVELS) + newList:SetFrameStrata("FULLSCREEN_DIALOG"); + newList:SetToplevel(true); + newList:Hide(); + newList:SetID(L_UIDROPDOWNMENU_MAXLEVELS); + newList:SetWidth(180) + newList:SetHeight(10) + for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do + --local newButton = CreateFrame("Button", "L_DropDownList"..L_UIDROPDOWNMENU_MAXLEVELS.."Button"..i, newList, "L_UIDropDownMenuButtonTemplate"); + local newButton = create_MenuButton("L_DropDownList"..L_UIDROPDOWNMENU_MAXLEVELS.."Button"..i, newList) + newButton:SetID(i); + end + end + + while ( index > L_UIDROPDOWNMENU_MAXBUTTONS ) do + L_UIDROPDOWNMENU_MAXBUTTONS = L_UIDROPDOWNMENU_MAXBUTTONS + 1; + for i=1, L_UIDROPDOWNMENU_MAXLEVELS do + --local newButton = CreateFrame("Button", "L_DropDownList"..i.."Button"..L_UIDROPDOWNMENU_MAXBUTTONS, _G["L_DropDownList"..i], "L_UIDropDownMenuButtonTemplate"); + local newButton = create_MenuButton("L_DropDownList"..i.."Button"..L_UIDROPDOWNMENU_MAXBUTTONS, _G["L_DropDownList"..i]) + newButton:SetID(L_UIDROPDOWNMENU_MAXBUTTONS); + end + end +end + +function lib:UIDropDownMenu_AddSeparator(level) + local separatorInfo = { + hasArrow = false; + dist = 0; + isTitle = true; + isUninteractable = true; + notCheckable = true; + iconOnly = true; + icon = "Interface\\Common\\UI-TooltipDivider-Transparent"; + tCoordLeft = 0; + tCoordRight = 1; + tCoordTop = 0; + tCoordBottom = 1; + tSizeX = 0; + tSizeY = 8; + tFitDropDownSizeX = true; + iconInfo = { + tCoordLeft = 0, + tCoordRight = 1, + tCoordTop = 0, + tCoordBottom = 1, + tSizeX = 0, + tSizeY = 8, + tFitDropDownSizeX = true + }, + }; + + lib:UIDropDownMenu_AddButton(separatorInfo, level); +end + +function lib:UIDropDownMenu_AddSpace(level) + local spaceInfo = { + hasArrow = false, + dist = 0, + isTitle = true, + isUninteractable = true, + notCheckable = true, + }; + + lib:UIDropDownMenu_AddButton(spaceInfo, level); +end + +function lib:UIDropDownMenu_AddButton(info, level) + --[[ + Might to uncomment this if there are performance issues + if ( not L_UIDROPDOWNMENU_OPEN_MENU ) then + return; + end + ]] + if ( not level ) then + level = 1; + end + + local listFrame = _G["L_DropDownList"..level]; + local index; + if (listFrame) then + index = listFrame.numButtons and (listFrame.numButtons + 1) or 1 + else + index = 0 + end + --local index = listFrame and (listFrame.numButtons + 1) or 1; + local width; + + delegateFrame:SetAttribute("createframes-level", level); + delegateFrame:SetAttribute("createframes-index", index); + delegateFrame:SetAttribute("createframes", true); + + listFrame = listFrame or _G["L_DropDownList"..level]; + local listFrameName = listFrame:GetName(); + + -- Set the number of buttons in the listframe + listFrame.numButtons = index; + + local button = _G[listFrameName.."Button"..index]; + local normalText = _G[button:GetName().."NormalText"]; + local icon = _G[button:GetName().."Icon"]; + -- This button is used to capture the mouse OnEnter/OnLeave events if the dropdown button is disabled, since a disabled button doesn't receive any events + -- This is used specifically for drop down menu time outs + local invisibleButton = _G[button:GetName().."InvisibleButton"]; + + -- Default settings + button:SetDisabledFontObject(GameFontDisableSmallLeft); + invisibleButton:Hide(); + button:Enable(); + + -- If not clickable then disable the button and set it white + if ( info.notClickable ) then + info.disabled = true; + button:SetDisabledFontObject(GameFontHighlightSmallLeft); + end + + -- Set the text color and disable it if its a title + if ( info.isTitle ) then + info.disabled = true; + button:SetDisabledFontObject(GameFontNormalSmallLeft); + end + + -- Disable the button if disabled and turn off the color code + if ( info.disabled ) then + button:Disable(); + invisibleButton:Show(); + info.colorCode = nil; + end + + -- If there is a color for a disabled line, set it + if( info.disablecolor ) then + info.colorCode = info.disablecolor; + end + + -- Configure button + if ( info.text ) then + -- look for inline color code this is only if the button is enabled + if ( info.colorCode ) then + button:SetText(info.colorCode..info.text.."|r"); + else + button:SetText(info.text); + end + + -- Set icon + if ( info.icon or info.mouseOverIcon ) then + icon:SetSize(16,16); + icon:SetTexture(info.icon); + icon:ClearAllPoints(); + icon:SetPoint("RIGHT"); + + if ( info.tCoordLeft ) then + icon:SetTexCoord(info.tCoordLeft, info.tCoordRight, info.tCoordTop, info.tCoordBottom); + else + icon:SetTexCoord(0, 1, 0, 1); + end + icon:Show(); + else + icon:Hide(); + end + + -- Check to see if there is a replacement font + if ( info.fontObject ) then + button:SetNormalFontObject(info.fontObject); + button:SetHighlightFontObject(info.fontObject); + else + button:SetNormalFontObject(GameFontHighlightSmallLeft); + button:SetHighlightFontObject(GameFontHighlightSmallLeft); + end + else + button:SetText(""); + icon:Hide(); + end + + button.iconOnly = nil; + button.icon = nil; + button.iconInfo = nil; + + if (info.iconInfo) then + icon.tFitDropDownSizeX = info.iconInfo.tFitDropDownSizeX; + else + icon.tFitDropDownSizeX = nil; + end + if (info.iconOnly and info.icon) then + button.iconOnly = true; + button.icon = info.icon; + button.iconInfo = info.iconInfo; + + lib:UIDropDownMenu_SetIconImage(icon, info.icon, info.iconInfo); + icon:ClearAllPoints(); + icon:SetPoint("LEFT"); + end + + -- Pass through attributes + button.func = info.func; + button.funcOnEnter = info.funcOnEnter; + button.funcOnLeave = info.funcOnLeave; + button.owner = info.owner; + button.hasOpacity = info.hasOpacity; + button.opacity = info.opacity; + button.opacityFunc = info.opacityFunc; + button.cancelFunc = info.cancelFunc; + button.swatchFunc = info.swatchFunc; + button.keepShownOnClick = info.keepShownOnClick; + button.tooltipTitle = info.tooltipTitle; + button.tooltipText = info.tooltipText; + button.tooltipInstruction = info.tooltipInstruction; + button.tooltipWarning = info.tooltipWarning; + button.arg1 = info.arg1; + button.arg2 = info.arg2; + button.hasArrow = info.hasArrow; + button.hasColorSwatch = info.hasColorSwatch; + button.notCheckable = info.notCheckable; + button.menuList = info.menuList; + button.tooltipWhileDisabled = info.tooltipWhileDisabled; + button.noTooltipWhileEnabled = info.noTooltipWhileEnabled; + button.tooltipOnButton = info.tooltipOnButton; + button.noClickSound = info.noClickSound; + button.padding = info.padding; + button.icon = info.icon; + button.mouseOverIcon = info.mouseOverIcon; + button.ignoreAsMenuSelection = info.ignoreAsMenuSelection; + + if ( info.value ) then + button.value = info.value; + elseif ( info.text ) then + button.value = info.text; + else + button.value = nil; + end + + local expandArrow = _G[listFrameName.."Button"..index.."ExpandArrow"]; + expandArrow:SetShown(info.hasArrow); + expandArrow:SetEnabled(not info.disabled); + + -- If not checkable move everything over to the left to fill in the gap where the check would be + local xPos = 5; + local yPos = -((button:GetID() - 1) * L_UIDROPDOWNMENU_BUTTON_HEIGHT) - L_UIDROPDOWNMENU_BORDER_HEIGHT; + local displayInfo = normalText; + if (info.iconOnly) then + displayInfo = icon; + end + + displayInfo:ClearAllPoints(); + if ( info.notCheckable ) then + if ( info.justifyH and info.justifyH == "CENTER" ) then + displayInfo:SetPoint("CENTER", button, "CENTER", -7, 0); + else + displayInfo:SetPoint("LEFT", button, "LEFT", 0, 0); + end + xPos = xPos + 10; + + else + xPos = xPos + 12; + displayInfo:SetPoint("LEFT", button, "LEFT", 20, 0); + end + + -- Adjust offset if displayMode is menu + local frame = L_UIDROPDOWNMENU_OPEN_MENU; + if ( frame and frame.displayMode == "MENU" ) then + if ( not info.notCheckable ) then + xPos = xPos - 6; + end + end + + -- If no open frame then set the frame to the currently initialized frame + frame = frame or L_UIDROPDOWNMENU_INIT_MENU; + + if ( info.leftPadding ) then + xPos = xPos + info.leftPadding; + end + button:SetPoint("TOPLEFT", button:GetParent(), "TOPLEFT", xPos, yPos); + + -- See if button is selected by id or name + if ( frame ) then + if ( lib:UIDropDownMenu_GetSelectedName(frame) ) then + if ( button:GetText() == lib:UIDropDownMenu_GetSelectedName(frame) ) then + info.checked = 1; + end + elseif ( lib:UIDropDownMenu_GetSelectedID(frame) ) then + if ( button:GetID() == lib:UIDropDownMenu_GetSelectedID(frame) ) then + info.checked = 1; + end + elseif ( lib:UIDropDownMenu_GetSelectedValue(frame) ) then + if ( button.value == lib:UIDropDownMenu_GetSelectedValue(frame) ) then + info.checked = 1; + end + end + end + + if not info.notCheckable then + local check = _G[listFrameName.."Button"..index.."Check"]; + local uncheck = _G[listFrameName.."Button"..index.."UnCheck"]; + if ( info.disabled ) then + check:SetDesaturated(true); + check:SetAlpha(0.5); + uncheck:SetDesaturated(true); + uncheck:SetAlpha(0.5); + else + check:SetDesaturated(false); + check:SetAlpha(1); + uncheck:SetDesaturated(false); + uncheck:SetAlpha(1); + end + + if info.customCheckIconAtlas or info.customCheckIconTexture then + check:SetTexCoord(0, 1, 0, 1); + uncheck:SetTexCoord(0, 1, 0, 1); + + if info.customCheckIconAtlas then + check:SetAtlas(info.customCheckIconAtlas); + uncheck:SetAtlas(info.customUncheckIconAtlas or info.customCheckIconAtlas); + else + check:SetTexture(info.customCheckIconTexture); + uncheck:SetTexture(info.customUncheckIconTexture or info.customCheckIconTexture); + end + elseif info.isNotRadio then + check:SetTexCoord(0.0, 0.5, 0.0, 0.5); + check:SetTexture("Interface\\Common\\UI-DropDownRadioChecks"); + uncheck:SetTexCoord(0.5, 1.0, 0.0, 0.5); + uncheck:SetTexture("Interface\\Common\\UI-DropDownRadioChecks"); + else + check:SetTexCoord(0.0, 0.5, 0.5, 1.0); + check:SetTexture("Interface\\Common\\UI-DropDownRadioChecks"); + uncheck:SetTexCoord(0.5, 1.0, 0.5, 1.0); + uncheck:SetTexture("Interface\\Common\\UI-DropDownRadioChecks"); + end + + -- Checked can be a function now + local checked = info.checked; + if ( type(checked) == "function" ) then + checked = checked(button); + end + + -- Show the check if checked + if ( checked ) then + button:LockHighlight(); + check:Show(); + uncheck:Hide(); + else + button:UnlockHighlight(); + check:Hide(); + uncheck:Show(); + end + else + _G[listFrameName.."Button"..index.."Check"]:Hide(); + _G[listFrameName.."Button"..index.."UnCheck"]:Hide(); + end + button.checked = info.checked; + + -- If has a colorswatch, show it and vertex color it + local colorSwatch = _G[listFrameName.."Button"..index.."ColorSwatch"]; + if ( info.hasColorSwatch ) then + if WoWClassic then + _G["L_DropDownList"..level.."Button"..index.."ColorSwatch".."NormalTexture"]:SetVertexColor(info.r, info.g, info.b); + else + _G["L_DropDownList"..level.."Button"..index.."ColorSwatch"].Color:SetVertexColor(info.r, info.g, info.b); + end + button.r = info.r; + button.g = info.g; + button.b = info.b; + colorSwatch:Show(); + else + colorSwatch:Hide(); + end + + lib:UIDropDownMenu_CheckAddCustomFrame(listFrame, button, info); + + button:SetShown(button.customFrame == nil); + + button.minWidth = info.minWidth; + + width = max(lib:UIDropDownMenu_GetButtonWidth(button), info.minWidth or 0); + --Set maximum button width + if ( width > (listFrame and listFrame.maxWidth or 0) ) then + listFrame.maxWidth = width; + end + + -- Set the height of the listframe + listFrame:SetHeight((index * L_UIDROPDOWNMENU_BUTTON_HEIGHT) + (L_UIDROPDOWNMENU_BORDER_HEIGHT * 2)); +end + +function lib:UIDropDownMenu_CheckAddCustomFrame(self, button, info) + local customFrame = info.customFrame; + button.customFrame = customFrame; + if customFrame then + customFrame:SetOwningButton(button); + customFrame:ClearAllPoints(); + customFrame:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0); + customFrame:Show(); + + lib:UIDropDownMenu_RegisterCustomFrame(self, customFrame); + end +end + +function lib:UIDropDownMenu_RegisterCustomFrame(self, customFrame) + self.customFrames = self.customFrames or {} + table.insert(self.customFrames, customFrame); +end + +function lib:UIDropDownMenu_GetMaxButtonWidth(self) + local maxWidth = 0; + for i=1, self.numButtons do + local button = _G[self:GetName().."Button"..i]; + local width = lib:UIDropDownMenu_GetButtonWidth(button); + if ( width > maxWidth ) then + maxWidth = width; + end + end + return maxWidth; +end + +function lib:UIDropDownMenu_GetButtonWidth(button) + local minWidth = button.minWidth or 0; + if button.customFrame and button.customFrame:IsShown() then + return math.max(minWidth, button.customFrame:GetPreferredEntryWidth()); + end + + if not button:IsShown() then + return 0; + end + + local width; + local buttonName = button:GetName(); + local icon = _G[buttonName.."Icon"]; + local normalText = _G[buttonName.."NormalText"]; + + if ( button.iconOnly and icon ) then + width = icon:GetWidth(); + elseif ( normalText and normalText:GetText() ) then + width = normalText:GetWidth() + 40; + + if ( button.icon ) then + -- Add padding for the icon + width = width + 10; + end + else + return minWidth; + end + + -- Add padding if has and expand arrow or color swatch + if ( button.hasArrow or button.hasColorSwatch ) then + width = width + 10; + end + if ( button.notCheckable ) then + width = width - 30; + end + if ( button.padding ) then + width = width + button.padding; + end + + return math.max(minWidth, width); +end + +function lib:UIDropDownMenu_Refresh(frame, useValue, dropdownLevel) + local maxWidth = 0; + local somethingChecked = nil; + if ( not dropdownLevel ) then + dropdownLevel = L_UIDROPDOWNMENU_MENU_LEVEL; + end + + local listFrame = _G["L_DropDownList"..dropdownLevel]; + listFrame.numButtons = listFrame.numButtons or 0; + -- Just redraws the existing menu + for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do + local button = _G["L_DropDownList"..dropdownLevel.."Button"..i]; + local checked = nil; + + if(i <= listFrame.numButtons) then + -- See if checked or not + if ( lib:UIDropDownMenu_GetSelectedName(frame) ) then + if ( button:GetText() == lib:UIDropDownMenu_GetSelectedName(frame) ) then + checked = 1; + end + elseif ( lib:UIDropDownMenu_GetSelectedID(frame) ) then + if ( button:GetID() == lib:UIDropDownMenu_GetSelectedID(frame) ) then + checked = 1; + end + elseif ( lib:UIDropDownMenu_GetSelectedValue(frame) ) then + if ( button.value == lib:UIDropDownMenu_GetSelectedValue(frame) ) then + checked = 1; + end + end + end + if (button.checked and type(button.checked) == "function") then + checked = button.checked(button); + end + + if not button.notCheckable and button:IsShown() then + -- If checked show check image + local checkImage = _G["L_DropDownList"..dropdownLevel.."Button"..i.."Check"]; + local uncheckImage = _G["L_DropDownList"..dropdownLevel.."Button"..i.."UnCheck"]; + if ( checked ) then + if not button.ignoreAsMenuSelection then + somethingChecked = true; + local icon = GetChild(frame, frame:GetName(), "Icon"); + if (button.iconOnly and icon and button.icon) then + lib:UIDropDownMenu_SetIconImage(icon, button.icon, button.iconInfo); + elseif ( useValue ) then + lib:UIDropDownMenu_SetText(frame, button.value); + icon:Hide(); + else + lib:UIDropDownMenu_SetText(frame, button:GetText()); + icon:Hide(); + end + end + button:LockHighlight(); + checkImage:Show(); + uncheckImage:Hide(); + else + button:UnlockHighlight(); + checkImage:Hide(); + uncheckImage:Show(); + end + end + + if ( button:IsShown() ) then + local width = lib:UIDropDownMenu_GetButtonWidth(button); + if ( width > maxWidth ) then + maxWidth = width; + end + end + end + if(somethingChecked == nil) then + lib:UIDropDownMenu_SetText(frame, VIDEO_QUALITY_LABEL6); + local icon = GetChild(frame, frame:GetName(), "Icon"); + icon:Hide(); + end + if (not frame.noResize) then + for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do + local button = _G["L_DropDownList"..dropdownLevel.."Button"..i]; + button:SetWidth(maxWidth); + end + lib:UIDropDownMenu_RefreshDropDownSize(_G["L_DropDownList"..dropdownLevel]); + end +end + +function lib:UIDropDownMenu_RefreshAll(frame, useValue) + for dropdownLevel = L_UIDROPDOWNMENU_MENU_LEVEL, 2, -1 do + local listFrame = _G["L_DropDownList"..dropdownLevel]; + if ( listFrame:IsShown() ) then + lib:UIDropDownMenu_Refresh(frame, nil, dropdownLevel); + end + end + -- useValue is the text on the dropdown, only needs to be set once + lib:UIDropDownMenu_Refresh(frame, useValue, 1); +end + +function lib:UIDropDownMenu_SetIconImage(icon, texture, info) + icon:SetTexture(texture); + if ( info.tCoordLeft ) then + icon:SetTexCoord(info.tCoordLeft, info.tCoordRight, info.tCoordTop, info.tCoordBottom); + else + icon:SetTexCoord(0, 1, 0, 1); + end + if ( info.tSizeX ) then + icon:SetWidth(info.tSizeX); + else + icon:SetWidth(16); + end + if ( info.tSizeY ) then + icon:SetHeight(info.tSizeY); + else + icon:SetHeight(16); + end + icon:Show(); +end + +function lib:UIDropDownMenu_SetSelectedName(frame, name, useValue) + frame.selectedName = name; + frame.selectedID = nil; + frame.selectedValue = nil; + lib:UIDropDownMenu_Refresh(frame, useValue); +end + +function lib:UIDropDownMenu_SetSelectedValue(frame, value, useValue) + -- useValue will set the value as the text, not the name + frame.selectedName = nil; + frame.selectedID = nil; + frame.selectedValue = value; + lib:UIDropDownMenu_Refresh(frame, useValue); +end + +function lib:UIDropDownMenu_SetSelectedID(frame, id, useValue) + frame.selectedID = id; + frame.selectedName = nil; + frame.selectedValue = nil; + lib:UIDropDownMenu_Refresh(frame, useValue); +end + +function lib:UIDropDownMenu_GetSelectedName(frame) + return frame.selectedName; +end + +function lib:UIDropDownMenu_GetSelectedID(frame) + if ( frame.selectedID ) then + return frame.selectedID; + else + -- If no explicit selectedID then try to send the id of a selected value or name + local listFrame = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL]; + for i=1, listFrame.numButtons do + local button = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL.."Button"..i]; + -- See if checked or not + if ( lib:UIDropDownMenu_GetSelectedName(frame) ) then + if ( button:GetText() == lib:UIDropDownMenu_GetSelectedName(frame) ) then + return i; + end + elseif ( lib:UIDropDownMenu_GetSelectedValue(frame) ) then + if ( button.value == lib:UIDropDownMenu_GetSelectedValue(frame) ) then + return i; + end + end + end + end +end + +function lib:UIDropDownMenu_GetSelectedValue(frame) + return frame.selectedValue; +end + +function lib:HideDropDownMenu(level) + local listFrame = _G["L_DropDownList"..level]; + listFrame:Hide(); +end + +function lib:ToggleDropDownMenu(level, value, dropDownFrame, anchorName, xOffset, yOffset, menuList, button, autoHideDelay) + if ( not level ) then + level = 1; + end + delegateFrame:SetAttribute("createframes-level", level); + delegateFrame:SetAttribute("createframes-index", 0); + delegateFrame:SetAttribute("createframes", true); + L_UIDROPDOWNMENU_MENU_LEVEL = level; + L_UIDROPDOWNMENU_MENU_VALUE = value; + local listFrameName = "L_DropDownList"..level; + local listFrame = _G[listFrameName]; + local tempFrame; + local point, relativePoint, relativeTo; + if ( not dropDownFrame ) then + tempFrame = button:GetParent(); + else + tempFrame = dropDownFrame; + end + if ( listFrame:IsShown() and (L_UIDROPDOWNMENU_OPEN_MENU == tempFrame) ) then + listFrame:Hide(); + else + -- Set the dropdownframe scale + local uiScale; + local uiParentScale = UIParent:GetScale(); + if ( GetCVar("useUIScale") == "1" ) then + uiScale = tonumber(GetCVar("uiscale")); + if ( uiParentScale < uiScale ) then + uiScale = uiParentScale; + end + else + uiScale = uiParentScale; + end + listFrame:SetScale(uiScale); + + -- Hide the listframe anyways since it is redrawn OnShow() + listFrame:Hide(); + + -- Frame to anchor the dropdown menu to + local anchorFrame; + + -- Display stuff + -- Level specific stuff + if ( level == 1 ) then + delegateFrame:SetAttribute("openmenu", dropDownFrame); + listFrame:ClearAllPoints(); + -- If there's no specified anchorName then use left side of the dropdown menu + if ( not anchorName ) then + -- See if the anchor was set manually using setanchor + if ( dropDownFrame.xOffset ) then + xOffset = dropDownFrame.xOffset; + end + if ( dropDownFrame.yOffset ) then + yOffset = dropDownFrame.yOffset; + end + if ( dropDownFrame.point ) then + point = dropDownFrame.point; + end + if ( dropDownFrame.relativeTo ) then + relativeTo = dropDownFrame.relativeTo; + else + relativeTo = GetChild(L_UIDROPDOWNMENU_OPEN_MENU, L_UIDROPDOWNMENU_OPEN_MENU:GetName(), "Left"); + end + if ( dropDownFrame.relativePoint ) then + relativePoint = dropDownFrame.relativePoint; + end + elseif ( anchorName == "cursor" ) then + relativeTo = nil; + local cursorX, cursorY = GetCursorPosition(); + cursorX = cursorX/uiScale; + cursorY = cursorY/uiScale; + + if ( not xOffset ) then + xOffset = 0; + end + if ( not yOffset ) then + yOffset = 0; + end + xOffset = cursorX + xOffset; + yOffset = cursorY + yOffset; + else + -- See if the anchor was set manually using setanchor + if ( dropDownFrame.xOffset ) then + xOffset = dropDownFrame.xOffset; + end + if ( dropDownFrame.yOffset ) then + yOffset = dropDownFrame.yOffset; + end + if ( dropDownFrame.point ) then + point = dropDownFrame.point; + end + if ( dropDownFrame.relativeTo ) then + relativeTo = dropDownFrame.relativeTo; + else + relativeTo = anchorName; + end + if ( dropDownFrame.relativePoint ) then + relativePoint = dropDownFrame.relativePoint; + end + end + if ( not xOffset or not yOffset ) then + xOffset = 8; + yOffset = 22; + end + if ( not point ) then + point = "TOPLEFT"; + end + if ( not relativePoint ) then + relativePoint = "BOTTOMLEFT"; + end + listFrame:SetPoint(point, relativeTo, relativePoint, xOffset, yOffset); + else + if ( not dropDownFrame ) then + dropDownFrame = L_UIDROPDOWNMENU_OPEN_MENU; + end + listFrame:ClearAllPoints(); + -- If this is a dropdown button, not the arrow anchor it to itself + if ( strsub(button:GetParent():GetName(), 0,14) == "L_DropDownList" and strlen(button:GetParent():GetName()) == 15 ) then + anchorFrame = button; + else + anchorFrame = button:GetParent(); + end + point = "TOPLEFT"; + relativePoint = "TOPRIGHT"; + listFrame:SetPoint(point, anchorFrame, relativePoint, 0, 0); + end + + -- Change list box appearance depending on display mode + if ( dropDownFrame and dropDownFrame.displayMode == "MENU" ) then + _G[listFrameName.."Border"]:Hide(); + _G[listFrameName.."MenuBackdrop"]:Show(); + else + _G[listFrameName.."Border"]:Show(); + _G[listFrameName.."MenuBackdrop"]:Hide(); + end + dropDownFrame.menuList = menuList; + lib:UIDropDownMenu_Initialize(dropDownFrame, dropDownFrame.initialize, nil, level, menuList); + -- If no items in the drop down don't show it + if ( listFrame.numButtons == 0 ) then + return; + end + + listFrame.onShow = dropDownFrame.listFrameOnShow; + + -- Check to see if the dropdownlist is off the screen, if it is anchor it to the top of the dropdown button + listFrame:Show(); + -- Hack since GetCenter() is returning coords relative to 1024x768 + local x, y = listFrame:GetCenter(); + -- Hack will fix this in next revision of dropdowns + if ( not x or not y ) then + listFrame:Hide(); + return; + end + + listFrame.onHide = dropDownFrame.onHide; + + + -- We just move level 1 enough to keep it on the screen. We don't necessarily change the anchors. + if ( level == 1 ) then + local offLeft = listFrame:GetLeft()/uiScale; + local offRight = (GetScreenWidth() - listFrame:GetRight())/uiScale; + local offTop = (GetScreenHeight() - listFrame:GetTop())/uiScale; + local offBottom = listFrame:GetBottom()/uiScale; + + local xAddOffset, yAddOffset = 0, 0; + if ( offLeft < 0 ) then + xAddOffset = -offLeft; + elseif ( offRight < 0 ) then + xAddOffset = offRight; + end + + if ( offTop < 0 ) then + yAddOffset = offTop; + elseif ( offBottom < 0 ) then + yAddOffset = -offBottom; + end + + listFrame:ClearAllPoints(); + if ( anchorName == "cursor" ) then + listFrame:SetPoint(point, relativeTo, relativePoint, xOffset + xAddOffset, yOffset + yAddOffset); + else + listFrame:SetPoint(point, relativeTo, relativePoint, xOffset + xAddOffset, yOffset + yAddOffset); + end + else + -- Determine whether the menu is off the screen or not + local offscreenY, offscreenX; + if ( (y - listFrame:GetHeight()/2) < 0 ) then + offscreenY = 1; + end + if ( listFrame:GetRight() > GetScreenWidth() ) then + offscreenX = 1; + end + if ( offscreenY and offscreenX ) then + point = gsub(point, "TOP(.*)", "BOTTOM%1"); + point = gsub(point, "(.*)LEFT", "%1RIGHT"); + relativePoint = gsub(relativePoint, "TOP(.*)", "BOTTOM%1"); + relativePoint = gsub(relativePoint, "(.*)RIGHT", "%1LEFT"); + xOffset = -11; + yOffset = -14; + elseif ( offscreenY ) then + point = gsub(point, "TOP(.*)", "BOTTOM%1"); + relativePoint = gsub(relativePoint, "TOP(.*)", "BOTTOM%1"); + xOffset = 0; + yOffset = -14; + elseif ( offscreenX ) then + point = gsub(point, "(.*)LEFT", "%1RIGHT"); + relativePoint = gsub(relativePoint, "(.*)RIGHT", "%1LEFT"); + xOffset = -11; + yOffset = 14; + else + xOffset = 0; + yOffset = 14; + end + + listFrame:ClearAllPoints(); + listFrame.parentLevel = tonumber(strmatch(anchorFrame:GetName(), "L_DropDownList(%d+)")); + listFrame.parentID = anchorFrame:GetID(); + listFrame:SetPoint(point, anchorFrame, relativePoint, xOffset, yOffset); + end + + end +end + +function lib:CloseDropDownMenus(level) + if ( not level ) then + level = 1; + end + for i=level, L_UIDROPDOWNMENU_MAXLEVELS do + _G["L_DropDownList"..i]:Hide(); + end + -- yes, we also want to close the menus which created by built-in UIDropDownMenus + for i=level, UIDROPDOWNMENU_MAXLEVELS do + _G["DropDownList"..i]:Hide(); + end +end + +local function containsMouse() + local result = false + + for i = 1, L_UIDROPDOWNMENU_MAXLEVELS do + local dropdown = _G["L_DropDownList"..i]; + if dropdown:IsShown() and dropdown:IsMouseOver() then + result = true; + end + end + for i = 1, UIDROPDOWNMENU_MAXLEVELS do + local dropdown = _G["DropDownList"..i]; + if dropdown:IsShown() and dropdown:IsMouseOver() then + result = true; + end + end + + + return result; +end + +function lib:UIDropDownMenu_HandleGlobalMouseEvent(button, event) + if event == "GLOBAL_MOUSE_DOWN" and (button == "LeftButton" or button == "RightButton") then + if not containsMouse() then + lib:CloseDropDownMenus(); + end + end +end + +-- hooking UIDropDownMenu_HandleGlobalMouseEvent +do + if lib and WoWRetail then + hooksecurefunc("UIDropDownMenu_HandleGlobalMouseEvent", function(button, event) + lib:UIDropDownMenu_HandleGlobalMouseEvent(button, event) + end) + + end +end + +function lib:UIDropDownMenu_SetWidth(frame, width, padding) + local frameName = frame:GetName(); + GetChild(frame, frameName, "Middle"):SetWidth(width); + local defaultPadding = 25; + if ( padding ) then + frame:SetWidth(width + padding); + else + frame:SetWidth(width + defaultPadding + defaultPadding); + end + if ( padding ) then + GetChild(frame, frameName, "Text"):SetWidth(width); + else + GetChild(frame, frameName, "Text"):SetWidth(width - defaultPadding); + end + frame.noResize = 1; +end + +function lib:UIDropDownMenu_SetButtonWidth(frame, width) + local frameName = frame:GetName(); + if ( width == "TEXT" ) then + width = GetChild(frame, frameName, "Text"):GetWidth(); + end + + GetChild(frame, frameName, "Button"):SetWidth(width); + frame.noResize = 1; +end + +function lib:UIDropDownMenu_SetText(frame, text) + local frameName = frame:GetName(); + GetChild(frame, frameName, "Text"):SetText(text); +end + +function lib:UIDropDownMenu_GetText(frame) + local frameName = frame:GetName(); + return GetChild(frame, frameName, "Text"):GetText(); +end + +function lib:UIDropDownMenu_ClearAll(frame) + -- Previous code refreshed the menu quite often and was a performance bottleneck + frame.selectedID = nil; + frame.selectedName = nil; + frame.selectedValue = nil; + lib:UIDropDownMenu_SetText(frame, ""); + + local button, checkImage, uncheckImage; + for i=1, L_UIDROPDOWNMENU_MAXBUTTONS do + button = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL.."Button"..i]; + button:UnlockHighlight(); + + checkImage = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL.."Button"..i.."Check"]; + checkImage:Hide(); + uncheckImage = _G["L_DropDownList"..L_UIDROPDOWNMENU_MENU_LEVEL.."Button"..i.."UnCheck"]; + uncheckImage:Hide(); + end +end + +function lib:UIDropDownMenu_JustifyText(frame, justification, customXOffset) + local frameName = frame:GetName(); + local text = GetChild(frame, frameName, "Text"); + text:ClearAllPoints(); + if ( justification == "LEFT" ) then + text:SetPoint("LEFT", GetChild(frame, frameName, "Left"), "LEFT", customXOffset or 27, 2); + text:SetJustifyH("LEFT"); + elseif ( justification == "RIGHT" ) then + text:SetPoint("RIGHT", GetChild(frame, frameName, "Right"), "RIGHT", customXOffset or -43, 2); + text:SetJustifyH("RIGHT"); + elseif ( justification == "CENTER" ) then + text:SetPoint("CENTER", GetChild(frame, frameName, "Middle"), "CENTER", customXOffset or -5, 2); + text:SetJustifyH("CENTER"); + end +end + +function lib:UIDropDownMenu_SetAnchor(dropdown, xOffset, yOffset, point, relativeTo, relativePoint) + dropdown.xOffset = xOffset; + dropdown.yOffset = yOffset; + dropdown.point = point; + dropdown.relativeTo = relativeTo; + dropdown.relativePoint = relativePoint; +end + +function lib:UIDropDownMenu_GetCurrentDropDown() + if ( L_UIDROPDOWNMENU_OPEN_MENU ) then + return L_UIDROPDOWNMENU_OPEN_MENU; + elseif ( L_UIDROPDOWNMENU_INIT_MENU ) then + return L_UIDROPDOWNMENU_INIT_MENU; + end +end + +function lib:UIDropDownMenuButton_GetChecked(self) + return _G[self:GetName().."Check"]:IsShown(); +end + +function lib:UIDropDownMenuButton_GetName(self) + return _G[self:GetName().."NormalText"]:GetText(); +end + +function lib:UIDropDownMenuButton_OpenColorPicker(self, button) + securecall("CloseMenus"); + if ( not button ) then + button = self; + end + L_UIDROPDOWNMENU_MENU_VALUE = button.value; + lib:OpenColorPicker(button); +end + +function lib:UIDropDownMenu_DisableButton(level, id) + _G["L_DropDownList"..level.."Button"..id]:Disable(); +end + +function lib:UIDropDownMenu_EnableButton(level, id) + _G["L_DropDownList"..level.."Button"..id]:Enable(); +end + +function lib:UIDropDownMenu_SetButtonText(level, id, text, colorCode) + local button = _G["L_DropDownList"..level.."Button"..id]; + if ( colorCode) then + button:SetText(colorCode..text.."|r"); + else + button:SetText(text); + end +end + +function lib:UIDropDownMenu_SetButtonNotClickable(level, id) + _G["L_DropDownList"..level.."Button"..id]:SetDisabledFontObject(GameFontHighlightSmallLeft); +end + +function lib:UIDropDownMenu_SetButtonClickable(level, id) + _G["L_DropDownList"..level.."Button"..id]:SetDisabledFontObject(GameFontDisableSmallLeft); +end + +function lib:UIDropDownMenu_DisableDropDown(dropDown) + local dropDownName = dropDown:GetName(); + local label = GetChild(dropDown, dropDownName, "Label"); + if label then + label:SetVertexColor(GRAY_FONT_COLOR:GetRGB()); + end + GetChild(dropDown, dropDownName, "Icon"):SetVertexColor(GRAY_FONT_COLOR:GetRGB()); + GetChild(dropDown, dropDownName, "Text"):SetVertexColor(GRAY_FONT_COLOR:GetRGB()); + GetChild(dropDown, dropDownName, "Button"):Disable(); + dropDown.isDisabled = 1; +end + +function lib:UIDropDownMenu_EnableDropDown(dropDown) + local dropDownName = dropDown:GetName(); + local label = GetChild(dropDown, dropDownName, "Label"); + if label then + label:SetVertexColor(NORMAL_FONT_COLOR:GetRGB()); + end + GetChild(dropDown, dropDownName, "Icon"):SetVertexColor(HIGHLIGHT_FONT_COLOR:GetRGB()); + GetChild(dropDown, dropDownName, "Text"):SetVertexColor(HIGHLIGHT_FONT_COLOR:GetRGB()); + GetChild(dropDown, dropDownName, "Button"):Enable(); + dropDown.isDisabled = nil; +end + +function lib:UIDropDownMenu_IsEnabled(dropDown) + return not dropDown.isDisabled; +end + +function lib:UIDropDownMenu_GetValue(id) + --Only works if the dropdown has just been initialized, lame, I know =( + local button = _G["L_DropDownList1Button"..id]; + if ( button ) then + return _G["L_DropDownList1Button"..id].value; + else + return nil; + end +end + +function lib:OpenColorPicker(info) + ColorPickerFrame.func = info.swatchFunc; + ColorPickerFrame.hasOpacity = info.hasOpacity; + ColorPickerFrame.opacityFunc = info.opacityFunc; + ColorPickerFrame.opacity = info.opacity; + ColorPickerFrame.previousValues = {r = info.r, g = info.g, b = info.b, opacity = info.opacity}; + ColorPickerFrame.cancelFunc = info.cancelFunc; + ColorPickerFrame.extraInfo = info.extraInfo; + -- This must come last, since it triggers a call to ColorPickerFrame.func() + ColorPickerFrame:SetColorRGB(info.r, info.g, info.b); + ShowUIPanel(ColorPickerFrame); +end + +function lib:ColorPicker_GetPreviousValues() + return ColorPickerFrame.previousValues.r, ColorPickerFrame.previousValues.g, ColorPickerFrame.previousValues.b; +end + +-- ////////////////////////////////////////////////////////////// +-- LibUIDropDownMenuTemplates +-- ////////////////////////////////////////////////////////////// + +-- Custom dropdown buttons are instantiated by some external system. +-- When calling L_UIDropDownMenu_AddButton that system sets info.customFrame to the instance of the frame it wants to place on the menu. +-- The dropdown menu creates its button for the entry as it normally would, but hides all elements. The custom frame is then anchored +-- to that button and assumes responsibility for all relevant dropdown menu operations. +-- The hidden button will request a size that it should become from the custom frame. + +lib.DropDownMenuButtonMixin = {} + +function lib.DropDownMenuButtonMixin:OnEnter(...) + ExecuteFrameScript(self:GetParent(), "OnEnter", ...); +end + +function lib.DropDownMenuButtonMixin:OnLeave(...) + ExecuteFrameScript(self:GetParent(), "OnLeave", ...); +end + +function lib.DropDownMenuButtonMixin:OnMouseDown(button) + if self:IsEnabled() then + lib:ToggleDropDownMenu(nil, nil, self:GetParent()); + PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON); + end +end + +lib.LargeDropDownMenuButtonMixin = CreateFromMixins(lib.DropDownMenuButtonMixin); + +function lib.LargeDropDownMenuButtonMixin:OnMouseDown(button) + if self:IsEnabled() then + local parent = self:GetParent(); + lib:ToggleDropDownMenu(nil, nil, parent, parent, -8, 8); + PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON); + end +end + +lib.DropDownExpandArrowMixin = {}; + +function lib.DropDownExpandArrowMixin:OnEnter() + local level = self:GetParent():GetParent():GetID() + 1; + + lib:CloseDropDownMenus(level); + + if self:IsEnabled() then + local listFrame = _G["L_DropDownList"..level]; + if ( not listFrame or not listFrame:IsShown() or select(2, listFrame:GetPoint()) ~= self ) then + lib:ToggleDropDownMenu(level, self:GetParent().value, nil, nil, nil, nil, self:GetParent().menuList, self); + end + end +end + +function lib.DropDownExpandArrowMixin:OnMouseDown(button) + if self:IsEnabled() then + lib:ToggleDropDownMenu(self:GetParent():GetParent():GetID() + 1, self:GetParent().value, nil, nil, nil, nil, self:GetParent().menuList, self); + PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON); + end +end + +lib.UIDropDownCustomMenuEntryMixin = {}; + +function lib.UIDropDownCustomMenuEntryMixin:GetPreferredEntryWidth() + -- NOTE: Only width is currently supported, dropdown menus size vertically based on how many buttons are present. + return self:GetWidth(); +end + +function lib.UIDropDownCustomMenuEntryMixin:OnSetOwningButton() + -- for derived objects to implement +end + +function lib.UIDropDownCustomMenuEntryMixin:SetOwningButton(button) + self:SetParent(button:GetParent()); + self.owningButton = button; + self:OnSetOwningButton(); +end + +function lib.UIDropDownCustomMenuEntryMixin:GetOwningDropdown() + return self.owningButton:GetParent(); +end + +function lib.UIDropDownCustomMenuEntryMixin:SetContextData(contextData) + self.contextData = contextData; +end + +function lib.UIDropDownCustomMenuEntryMixin:GetContextData() + return self.contextData; +end + +-- ////////////////////////////////////////////////////////////// +-- L_UIDropDownCustomMenuEntryTemplate +function lib:Create_UIDropDownCustomMenuEntry(name, parent) + local f = _G[name] or CreateFrame("Frame", name, parent or nil) + f:EnableMouse(true) + f:Hide() + + -- I am not 100% sure if below works for replacing the mixins + f:SetScript("GetPreferredEntryWidth", function(self) + return self:GetWidth() + end) + f:SetScript("SetOwningButton", function(self, button) + self:SetParent(button:GetParent()) + self.owningButton = button + self:OnSetOwningButton() + end) + f:SetScript("GetOwningDropdown", function(self) + return self.owningButton:GetParent() + end) + f:SetScript("SetContextData", function(self, contextData) + self.contextData = contextData + end) + f:SetScript("GetContextData", function(self) + return self.contextData + end) + + return f +end + +-- ////////////////////////////////////////////////////////////// +-- UIDropDownMenuButtonScriptTemplate +-- +-- TBD +-- + +-- ////////////////////////////////////////////////////////////// +-- LargeUIDropDownMenuTemplate +-- +-- TBD +-- + +-- ////////////////////////////////////////////////////////////// +-- EasyMenu +-- Simplified Menu Display System +-- This is a basic system for displaying a menu from a structure table. +-- +-- Args: +-- menuList - menu table +-- menuFrame - the UI frame to populate +-- anchor - where to anchor the frame (e.g. CURSOR) +-- x - x offset +-- y - y offset +-- displayMode - border type +-- autoHideDelay - how long until the menu disappears +local function easyMenu_Initialize( frame, level, menuList ) + for index = 1, #menuList do + local value = menuList[index] + if (value.text) then + value.index = index; + lib:UIDropDownMenu_AddButton( value, level ); + end + end +end + +function lib:EasyMenu(menuList, menuFrame, anchor, x, y, displayMode, autoHideDelay ) + if ( displayMode == "MENU" ) then + menuFrame.displayMode = displayMode; + end + lib:UIDropDownMenu_Initialize(menuFrame, easyMenu_Initialize, displayMode, nil, menuList); + lib:ToggleDropDownMenu(1, nil, menuFrame, anchor, x, y, menuList, nil, autoHideDelay); +end + +function lib:EasyMenu_Initialize( frame, level, menuList ) + easyMenu_Initialize( frame, level, menuList ) +end + diff --git a/Libraries/LibUIDropDownMenu/LibUIDropDownMenu.xml b/Libraries/LibUIDropDownMenu/LibUIDropDownMenu.xml index 758809e..bc7d69e 100644 --- a/Libraries/LibUIDropDownMenu/LibUIDropDownMenu.xml +++ b/Libraries/LibUIDropDownMenu/LibUIDropDownMenu.xml @@ -1,25 +1,24 @@ - - -