Back to Home

ESO Lua File v101041

libraries/utility/zo_hook.lua

[◄ back to folders ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
--[[
Hooking API (holding off on generalization until we see everything that needs to use it...
--]]
-- Install a handler that will be called before the original function and whose return value will decide if the original even needs to be called.
-- If the hook returns true it means that the hook handled the call entirely, and the original doesn't need calling.
-- ZO_PreHook can be called with or without an objectTable; if the argument is a string (the function name), it just uses _G
function ZO_PreHook(objectTable, existingFunctionName, hookFunction)
    if type(objectTable) == "string" then
        hookFunction = existingFunctionName
        existingFunctionName = objectTable
        objectTable = _G
    end
     
    local existingFn = objectTable[existingFunctionName]
    if existingFn ~= nil and type(existingFn) == "function" then    
        local newFn =   function(...)
                            if not hookFunction(...) then
                                return existingFn(...)
                            end
                        end
        objectTable[existingFunctionName] = newFn
    end
    return existingFn
end
function ZO_PostHook(objectTable, existingFunctionName, hookFunction)
    if type(objectTable) == "string" then
        hookFunction = existingFunctionName
        existingFunctionName = objectTable
        objectTable = _G
    end
     
    local existingFn = objectTable[existingFunctionName]
    if existingFn ~= nil and type(existingFn) == "function" then    
        local newFn =   function(...)
                            local returns = {existingFn(...)}
                            hookFunction(...)
                            return unpack(returns)
                        end
        objectTable[existingFunctionName] = newFn
    end
    return existingFn
end
function ZO_PreHookHandler(control, handlerName, hookFunction)
    local existingHandlerFunction = control:GetHandler(handlerName)
    local newHandlerFunction
    if existingHandlerFunction then
        newHandlerFunction = function(...)
            if not hookFunction(...) then
                return existingHandlerFunction(...)
            end
        end
    else
        newHandlerFunction = hookFunction
    end
    control:SetHandler(handlerName, newHandlerFunction)
    return existingHandlerFunction
end
function ZO_PostHookHandler(control, handlerName, hookFunction)
    local existingHandlerFunction = control:GetHandler(handlerName)
    local newHandlerFunction
    if existingHandlerFunction then
        newHandlerFunction = function(...)
            local returns = {existingHandlerFunction(...)}
            hookFunction(...)
            return unpack(returns)
        end
    else
        newHandlerFunction = hookFunction
    end
    control:SetHandler(handlerName, newHandlerFunction)
    return existingHandlerFunction
end
--where ... are the handler args after self
-- ZO_PropagateHandler(self:GetParent(), "OnMouseUp", button, upInside)
function ZO_PropagateHandler(propagateTo, handlerName, ...)
    if propagateTo then
        -- TODO: Determine whether handlers of any namespace should be called;
        -- new Control methods - GetNumHandlers and GetHandlerByIndex - would be required to do so.
        local handler = propagateTo:GetHandler(handlerName)
        if handler then
            handler(propagateTo, ...)
            return true
        end
    end
    return false
end
-- Propagates a call to the specified handler name from a control to each ancestor control
-- that defines a corresponding handler function until and excluding the owning window.
-- ZO_PropagateHandlerToAllAncestors("OnMouseUp", ...)
function ZO_PropagateHandlerToAllAncestors(handlerName, propagateFromControl, ...)
    local owningWindow = propagateFromControl:GetOwningWindow()
    local ancestorControl = propagateFromControl:GetParent()
    -- No semaphore mechanism is currently required to suppress reentrant calls when
    -- ancestor controls also inherit ZO_PropagateMouseOverBehaviorToAllAncestors
    -- because, at present, ZO_PropagateHandler only propagates events to handlers
    -- that have no namespace. A gating mechanism would be required should this
    -- paradigm ever change.
    while ancestorControl and ancestorControl ~= owningWindow do
        ZO_PropagateHandler(ancestorControl, handlerName, ...)
        ancestorControl = ancestorControl:GetParent()
    end
end
-- Propagates a call to the specified handler name from a control to the nearest ancestor control
-- that defines a corresponding handler function until and excluding the owning window.
-- ZO_PropagateHandlerToNearestAncestor("OnMouseUp", ...)
function ZO_PropagateHandlerToNearestAncestor(handlerName, propagateFromControl, ...)
    local owningWindow = propagateFromControl:GetOwningWindow()
    local ancestorControl = propagateFromControl:GetParent()
    while ancestorControl and ancestorControl ~= owningWindow and not ZO_PropagateHandler(ancestorControl, handlerName, ...) do
        ancestorControl = ancestorControl:GetParent()
    end
end
-- For when you want to propagate to the control's parent without breaking self out of the args
-- ZO_PropagateHandlerToParent("OnMouseUp", ...)
function ZO_PropagateHandlerToParent(handlerName, propagateFromControl, ...)
    ZO_PropagateHandler(propagateFromControl:GetParent(), handlerName, ...)
end
-- For when you want to propagate without breaking self out of the args
-- ZO_PropagateHandlerFromControl(self:GetParent():GetParent(), "OnMouseUp", ...)
function ZO_PropagateHandlerFromControl(propagateToControl, handlerName, propagateFromControl, ...)
    ZO_PropagateHandler(propagateToControl, handlerName, ...)
end
--[[
Updates the metatable of the specified control to forward calls to any umimplemented
methods to any of the specified control(s) that have a corresponding implementation.
Invocation of a relayed method will return the return value of each target objects'
implementation in the order that the target objects were specified.
For example:
-- Forward unimplemented method calls to the container control's child label control.
local containerControl = self.control:GetNamedChild("PlayerContainer")
local labelControl = containerControl:GetNamedChild("Name")
ZO_ForwardUnimplementedMethodsForControl(containerControl, labelControl)
-- Call label control-specific methods of the child label control via its parent container control.
containerControl:SetFont("ZoFontGameBold")
containerControl:SetText(playerName)
]]
do
    local UNIMPLEMENTED_METHOD_SENTINEL = {}
    local function GetOrCreateMethodRelayOrUnimplementedSentinel(key, targetObjects)
        -- Identify any target objects with a corresponding method implementation for this key.
        local objectsWithImplementations = {}
        for _, targetObject in ipairs(targetObjects) do
            local targetMethod = targetObject[key]
            if targetMethod and type(targetMethod) == "function" then
                table.insert(objectsWithImplementations, targetObject)
            end
        end
        if not next(objectsWithImplementations) then
            -- No target objects have a corresponding method implementation for this key.
            return UNIMPLEMENTED_METHOD_SENTINEL
        end
        -- Create a relay method that will forward the invocation of this method to
        -- the supporting target objects and return the first return value of each
        -- forwarded call in the order in which the methods are invoked.
        return function(...)
            local returnValues = {}
            for index, targetObject in ipairs(objectsWithImplementations) do
                -- Relay the call to the target object with a reference to the
                -- target object substituted in place of the 'self' parameter.
                local targetMethod = targetObject[key]
                returnValues[index] = targetMethod(targetObject, select(2, ...))
            end
            return unpack(returnValues)
        end
    end
    function ZO_ForwardUnimplementedMethodsForControl(sourceControl, ...)
        -- Wrap the source control's existing metatable indexer method so that keys not found
        -- in the source control are subsequently searched for within the target object(s).
        -- A relay method is created, cached, and returned for any target objects that have a
        -- method implementation for a given key; if no method implementations are found,
        -- a sentinel value is cached to prevent unnecessary lookups for that key.
        local targetObjects = {...}
        local relayMethods = {}
        local metaTable = getmetatable(sourceControl)
        local originalIndex = rawget(metaTable, "__index")
        local indexWrapper = function(tbl, key)
            if originalIndex then
                local value = originalIndex[key]
                if value ~= nil then
                    -- Return the corresponding value for existing keys.
                    return value
                end
            end
            local relayMethod = relayMethods[key]
            if relayMethod == nil then
                -- Create either a relay method that forwards this invocation to
                -- supporting target objects or a sentinel value that signals that
                -- no target objects have a corresponding implementation.
                relayMethod = GetOrCreateMethodRelayOrUnimplementedSentinel(key, targetObjects)
                relayMethods[key] = relayMethod
            end
            if relayMethod == UNIMPLEMENTED_METHOD_SENTINEL then
                -- An attempt was already made to index this key but no
                -- implementation was found. Suppress further attempts.
                return nil
            end
            return relayMethod
        end
        rawset(metaTable, "__index", indexWrapper)
    end
end