Thread Tools Display Modes
02-27-17, 09:43 PM   #1
seyan777
An Aku'mai Servant
Join Date: Feb 2017
Posts: 35
Extending/Hooking each widgets' Blizzard default API

Wassup!

Again, the title says pretty much all.

What I am trying to achieve here is to create a new function or extend an existing function of widget objects.

However, each widget objects won't have all the same functions.

For example, Frame object will have :StartMoving() & :StopMovingOrSizing(), while FontString object won't.

I am aware of that by accessing metatable of each objects, you can modify or create a function.

The only problem that I am currently facing is getting a list of all widget objects without hard coding them.

Any ideas, please?

Cheers!
  Reply With Quote
02-27-17, 09:50 PM   #2
Lombra
A Molten Giant
 
Lombra's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2006
Posts: 554
I don't understand what you're looking for exactly. A list of object types, such as Frame, Button, Texture, etc? What are you doing that requires that which you are looking for?
__________________
Grab your sword and fight the Horde!
  Reply With Quote
02-27-17, 10:33 PM   #3
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
It's very unclear what exactly you want to achieve, so it might be better if you provide a use case since there might be better ways to do it than hooking metatable functions.
__________________

Last edited by MunkDev : 02-28-17 at 12:35 AM. Reason: SDPhantom's answer is better.
  Reply With Quote
02-28-17, 12:09 AM   #4
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
You could do something like this.
Lua Code:
  1. local WidgetMeta={};
  2. for frame in function(_,last) return EnumerateFrames(last); end do
  3.     local objtype=frame:GetObjectType();
  4.     if not WidgetMeta[objtype] then WidgetMeta=getmetatable(frame).__index; end
  5.  
  6.     for _,region in ipairs({frame:GetRegions()}) do
  7.         local objtype=region:GetObjectType();
  8.         if not WidgetMeta[objtype] then WidgetMeta=getmetatable(region).__index; end
  9.     end
  10. end

This should grab every widget type, except maybe the animation system, and put their metatable functions in WidgetMeta. Note, frame:GetChildren() scanning is omitted because EnumerateFrames() covers them anyway.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
02-28-17, 12:59 AM   #5
seyan777
An Aku'mai Servant
Join Date: Feb 2017
Posts: 35
Okay, I'll try to be more specific!

Let's say I want to modify the :StartMoving() function for all widget objects which owns that function.
(Objects may include "Frame", "Button", "StatusBar", etc.)

Like MunkDev stated, I could access index of each object types' metatable to modify that function.

Lua Code:
  1. local oldFunctions = oldFunctions or {};
  2.  
  3. local function _StartMoving(self)
  4.     oldFunctions[self:GetObjectType()]["OldStartMoving"](self);
  5.  
  6.     -- do some extra stuff here
  7. end
  8.  
  9. local index = getmetatable(object).__index;
  10.  
  11. oldFunctions[object:GetObjectType()] = oldFunctions[object:GetObjectType()] or {};
  12.  
  13. if object.StartMoving then
  14.     oldFunctions[object:GetObjectType()]["OldStartMoving"] = object.StartMoving;
  15.  
  16.     index.StartMoving = _StartMoving;
  17. end

So, now I need a list of object types to apply this approach, but that's exactly where I am having difficulties with.

TL;DR: Need a method to get all object types.

Cheers!
  Reply With Quote
02-28-17, 01:01 AM   #6
seyan777
An Aku'mai Servant
Join Date: Feb 2017
Posts: 35
Originally Posted by SDPhantom View Post
You could do something like this.
Lua Code:
  1. local WidgetMeta={};
  2. for frame in function(_,last) return EnumerateFrames(last); end do
  3.     local objtype=frame:GetObjectType();
  4.     if not WidgetMeta[objtype] then WidgetMeta=getmetatable(frame).__index; end
  5.  
  6.     for _,region in ipairs({frame:GetRegions()}) do
  7.         local objtype=region:GetObjectType();
  8.         if not WidgetMeta[objtype] then WidgetMeta=getmetatable(region).__index; end
  9.     end
  10. end

This should grab every widget type, except maybe the animation system, and put their metatable functions in WidgetMeta. Note, frame:GetChildren() scanning is omitted because EnumerateFrames() covers them anyway.
Awesome!

Since, what I needed was a widget type rather than functions I have modified it to following and works like a charm

Lua Code:
  1. local WidgetMeta = {};
  2. for frame in function(_, last) return EnumerateFrames(last); end do
  3.     local objtype = frame:GetObjectType();
  4.  
  5.     if not WidgetMeta[objtype] then
  6.         WidgetMeta[objtype] = true;
  7.     end
  8.  
  9.     for _, region in ipairs({frame:GetRegions()}) do
  10.         local objtype = region:GetObjectType();
  11.  
  12.         if not WidgetMeta[objtype] then
  13.             WidgetMeta[objtype] = true;
  14.         end
  15.     end
  16. end

Thank you, SDPhantom!

But, I still don't get how that annoymous function in first for loop works.

Where does it get those parameters (_ and last)?

Last edited by seyan777 : 02-28-17 at 01:03 AM.
  Reply With Quote
02-28-17, 04:20 AM   #7
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
Originally Posted by seyan777 View Post
But, I still don't get how that annoymous function in first for loop works.

Where does it get those parameters (_ and last)?
It's a custom iterator. EnumerateFrames is a utility function that works similarly to next, in that it returns the first frame in a list, and when you provide the returned frame back to the same function, it'll return the second frame in the list. It'll keep doing this until it runs out of frames.

This could otherwise be implemented as a while loop:
Lua Code:
  1. local numFrames = 0
  2. local frame = EnumerateFrames()
  3. while frame do
  4.     numFrames = numFrames + 1
  5.     frame = EnumerateFrames(frame)
  6. end
  7.  
  8. print(numFrames)
__________________
  Reply With Quote
02-28-17, 06:27 AM   #8
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
Originally Posted by seyan777 View Post
Let's say I want to modify the :StartMoving() function for all widget objects which owns that function.
(Objects may include "Frame", "Button", "StatusBar", etc.)

Like MunkDev stated, I could access index of each object types' metatable to modify that function.

Lua Code:
  1. local oldFunctions = oldFunctions or {};
  2.  
  3. local function _StartMoving(self)
  4.     oldFunctions[self:GetObjectType()]["OldStartMoving"](self);
  5.  
  6.     -- do some extra stuff here
  7. end
  8.  
  9. local index = getmetatable(object).__index;
  10.  
  11. oldFunctions[object:GetObjectType()] = oldFunctions[object:GetObjectType()] or {};
  12.  
  13. if object.StartMoving then
  14.     oldFunctions[object:GetObjectType()]["OldStartMoving"] = object.StartMoving;
  15.  
  16.     index.StartMoving = _StartMoving;
  17. end
A more efficient way to go at this would be to combine the code I already posted with the following:
Lua Code:
  1. local Hooks={};
  2.  
  3. function Hooks:StartMoving()
  4. --  Do something with frame:StartMoving()
  5. end
  6.  
  7. function Hooks:StopMovingOrSizing()
  8. --  Do something with frame:StopMovingOrSizing()
  9. end
  10.  
  11. for key,func in pairs(Hooks) do
  12.     for _,meta in pairs(WidgetMeta) do
  13.         if meta[key] then hooksecurefunc(meta,key,func); end
  14.     end
  15. end
This goes through all of the metatables and hooks the functions matching the name stored in Hooks. No need to create or search for the widget type you need to hook or grab their metatables since that was already done. Using hooksecurefunc() also deals with setting up the hooks and does so without causing taint that could possibly cripple the rest of the UI.



Originally Posted by seyan777 View Post
But, I still don't get how that annoymous function in first for loop works.
Where does it get those parameters (_ and last)?
Those two parameters for the first iteration are given from what's supplied after the function in the for declaration. Since these are both omitted, they'll be nil for the first run. For each iteration after, the first parameter will be the same while the second will be what this function returned the last time. If the function returns multiple values, this will always be the first one. The loop ends when the function returns nil.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 02-28-17 at 06:52 AM.
  Reply With Quote
02-28-17, 11:24 PM   #9
seyan777
An Aku'mai Servant
Join Date: Feb 2017
Posts: 35
Cool!

Many thanks to both MunkDev and SDPhantom

You guys made my day !!
  Reply With Quote
03-01-17, 12:52 AM   #10
seyan777
An Aku'mai Servant
Join Date: Feb 2017
Posts: 35
Originally Posted by SDPhantom View Post
A more efficient way to go at this would be to combine the code I already posted with the following:
Lua Code:
  1. local Hooks={};
  2.  
  3. function Hooks:StartMoving()
  4. --  Do something with frame:StartMoving()
  5. end
  6.  
  7. function Hooks:StopMovingOrSizing()
  8. --  Do something with frame:StopMovingOrSizing()
  9. end
  10.  
  11. for key,func in pairs(Hooks) do
  12.     for _,meta in pairs(WidgetMeta) do
  13.         if meta[key] then hooksecurefunc(meta,key,func); end
  14.     end
  15. end
This goes through all of the metatables and hooks the functions matching the name stored in Hooks. No need to create or search for the widget type you need to hook or grab their metatables since that was already done. Using hooksecurefunc() also deals with setting up the hooks and does so without causing taint that could possibly cripple the rest of the UI.
Just tested it now and am facing an issue with the nested for loop.

Lua Code:
  1. Hooks = {};
  2. WidgetMeta = {};
  3.  
  4. function Hooks:StartMoving(obj)
  5.     print("Hooks:StartMoving");
  6. end
  7.  
  8. function Hooks:StopMovingOrSizing(obj)
  9.     print("Hooks:StopMovingOrSizing");
  10. end
  11.  
  12. for frame in function(_, last) return EnumerateFrames(last); end do
  13.     local objtype = frame:GetObjectType();
  14.  
  15.     if not WidgetMeta[objtype] then
  16.         WidgetMeta = getmetatable(frame).__index;
  17.     end
  18.  
  19.     for _, region in ipairs({frame:GetRegions()}) do
  20.         local objtype = region:GetObjectType();
  21.  
  22.         if not WidgetMeta[objtype] then
  23.             WidgetMeta = getmetatable(region).__index;
  24.         end
  25.     end
  26. end
  27.  
  28. for key, func in pairs(Hooks) do
  29.     for _, meta in pairs(WidgetMeta) do
  30.         if meta[key] then
  31.             hooksecurefunc(meta, key, func);
  32.         end
  33.     end
  34. end

Since meta is in fact a function itself, if statement on inner for loop errors out as it is trying to index a function.

Thus, I have switched that part to following:

Lua Code:
  1. for key, func in pairs(Hooks) do
  2.     if WidgetMeta[key] then
  3.         hooksecurefunc(WidgetMeta, key, func);
  4.     end
  5. end

No errors popped up!

However, whenever :StartMoving() & :StopMovingOrSizing() functions were called, it did not print out those strings.

So, I checked WidgetMeta table and it did not have :StartMoving() & :StopMovingOrSizing() functions

Any ideas, please?!

Cheers!

Last edited by seyan777 : 03-01-17 at 01:03 AM.
  Reply With Quote
03-01-17, 06:04 AM   #11
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
You forgot the indices when copying from the metatable on the widget to your table. You keep overwriting the table with whatever the last frame/region was in your loop. Note that meta is a function in your example because you made this error. You're actually looping over a metatable, not a table of metatables. In the example below, meta will always be a table.

Lua Code:
  1. Hooks = {};
  2. WidgetMeta = {};
  3.  
  4. function Hooks:StartMoving() -- when using colon, the object itself will be accessible as self
  5.     print("Hooks:StartMoving", self:GetName());
  6. end
  7.  
  8. function Hooks:StopMovingOrSizing()
  9.     print("Hooks:StopMovingOrSizing", self:GetName());
  10. end
  11.  
  12. for frame in function(_, last) return EnumerateFrames(last); end do
  13.     local objtype = frame:GetObjectType();
  14.  
  15.     if not WidgetMeta[objtype] then
  16.         WidgetMeta[objtype] = getmetatable(frame).__index;  -- fixed
  17.     end
  18.  
  19.     for _, region in ipairs({frame:GetRegions()}) do
  20.         local objtype = region:GetObjectType();
  21.  
  22.         if not WidgetMeta[objtype] then
  23.             WidgetMeta[objtype] = getmetatable(region).__index; -- fixed
  24.         end
  25.     end
  26. end
  27.  
  28. for key, func in pairs(Hooks) do
  29.     for _, meta in pairs(WidgetMeta) do
  30.         if meta[key] then
  31.             hooksecurefunc(meta, key, func);
  32.         end
  33.     end
  34. end
__________________

Last edited by MunkDev : 03-01-17 at 06:11 AM.
  Reply With Quote
03-01-17, 06:19 AM   #12
seyan777
An Aku'mai Servant
Join Date: Feb 2017
Posts: 35
Originally Posted by MunkDev View Post
You forgot the indices when copying from the metatable on the widget to your table. You keep overwriting the table with whatever the last frame/region was in your loop. Note that meta is a function in your example because you made this error. You're actually looping over a metatable, not a table of metatables. In the example below, meta will always be a table.
That depresses me so much

How stupid I could be...

Thanks for correcting it, MunkDev!!
  Reply With Quote

WoWInterface » Developer Discussions » General Authoring Discussion » Extending/Hooking each widgets' Blizzard default API


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off