I've been trying to design a library to dynamically create UIs (frames) based on a configuration table. LibFrameX-1.0
My first expectation upon seeing the word library meant a reusable "instance" that held properties of its own. However, after having read more and more into it, it seems that libraries share their properties with all other addons using it; like static members of a class.
Ex:
- MyAddon1 and MyAddon2 both use MyLib1
- MyLib1 has a local table called tf and a function MyLib1:FillTable(t)
- MyAddon1 instantiates ml1 = LibStub:NewLibrary("MyLib1") and calls ml1:FillTable passing a table that looks like this t = {1,2,3}
Here's where it gets interesting...
- MyAddon2 also tries to instantiate MyLib1, however, MyLib1 already exists and its major and minor are valid so no need to upgrade. This I understand and this, I think, has answered my question.
- When MyAddon2 calls ml2:FillTable, with t = {4,5,6}, it overwrites the contents of tf (originally 1,2,3) with 4,5,6.
My question is this.
I can segregate each passed tf from the calling addons into its own index using the addon's name or whatever string the user decides... But won't this be a size issue? (15-20 addons all having their config in LibFrameX???)
fx = LibStub:NewLibrary("LibFrameX-1.0")
local fx.data = fx.data or {}
fx:FillTable(addon, t)
self.data[addon].tf = t
end
Or is there a better solution to this... Maybe the whole idea is evil...
Any thoughts?
Edit: One thought I have is to create a table in the addon, pass it to LibFrameX, have the lib return a fully created frame structure to the addon.
fx:FillTable(t, tf)
for i = 1, #t do
... processing done here
end
return tf
end
Then it would be the addon responsible to manage it's frames using functionality of the lib. The only downfall to this approach is that I would need to pass tf to every lib function call. >:(
Wouldn't you need to provide the addon index (self) to every function being called in order to access the right node in the lib data table; like all getter/setter functions?
Wouldn't you need to provide the addon index (self) to every function being called in order to access the right node in the lib data table; like all getter/setter functions?
No. Don't get fooled by the colon/method syntactic sugar.
myAddon:SetFoo(5)
is the same as
myAddon.SetFoo(myAddon,5)
which is the same as (because it was assigned to be the same as)
I was referring to the get/set functions inside the lib, not the ones called in the addon. My bad; didn't word it correctly.
Here's my code.
local LIBFRAMEX = "LibFrameX-1.0"
local LIBFRAMEX_MINOR = tonumber(("$Revision: 7 $"):match("(%d+)"))
assert(LibStub, LIBFRAMEX .. " requires LibStub.")
local lfx = LibStub:NewLibrary(LIBFRAMEX, LIBFRAMEX_MINOR)
if not lfx then return end
lfx.data = lfx.data or {}
------------------------------------------------------------
-- Function to create a frame with values passed from FrameX:makeFrames
-- Arguments:
-- - v: type tframe table/child table |table|
-- - p: Parent frame |Frame|
-- Return: - the configured frame |Frame|
-- - nil on error
------------------------------------------------------------
local function genFrame(v, p)
assert(v.name and v.name ~= "", "Can't process a node without a name.") -- error, unnamed node
-- create frame
local fo = CreateFrame(v.ftype, v.name, p, v.template) -- type, name, parent, template
assert(fo, "Failed to create frame " .. v.name) -- failed to create the frame
-- set parameters
fo.root = v.root or false -- make sure we set root to false if not supplied
fo.name = v.name
fo.rgba = v.rgba
fo.brgba = v.brgba
fo.bd = v.bd
fo.dim = v.dim
fo.text = v.text
fo.show = v.show or true -- make sure we set show to true if not supplied
-- draw background if any
if not fo.root and (fo.bd or fo.rgba or fo.brgba) then
fo:SetBackdrop(v.bd)
fo:SetBackdropColor(v.rgba.r/256, v.rgba.g/256, v.rgba.b/256, v.rgba.a)
fo:SetBackdropBorderColor(v.brgba.r/256, v.brgba.g/256, v.brgba.b/256, v.brgba.a)
end
-- prepare to set dimensions
if v.dim then
fo:SetWidth(v.dim.w)
fo:SetHeight(v.dim.h)
fo:SetScale(v.dim.s)
end
-- show the frame
if fo.show == true then
fo:Show()
else
fo:Hide()
end
return fo
end
------------------------------------------------------------
-- function to load frame data
-- Recursive function
-- Arguments:
-- - t: type tframe table/child table |table|
-- - p: Parent frame |Frame|
-- Return: - the frame processed |Frame|
-- - nil on error
------------------------------------------------------------
local function ProcessConfigData(cfg, p)
-- loop through all elements and recursively create all frames
local fx = {} -- table to return (represents a node with or without children)
for i = 1, #cfg do
assert(cfg[i], "Invalid config item.")
-- start filling up data structure for this frame
local v = cfg[i]
local fx = {}
fx[i] = genFrame(v, p) -- create the frame
assert(fx[i], "Error generating frame.")
local fxi = fx[i]
fxi:SetPoint(v.pos.a, v.pos.r, v.pos.rp, v.pos.x, v.pos.y) -- align, relative, rel parent, x, y
-- check if the frame has a text component
if v.text and v.text.show then
local text = fxi.text
text = fxi:CreateFontString(nil, "ARTWORK", v.fs or "NumberFont_Shadow_Small")
text:SetAllPoints(true)
text:SetJustifyH(v.halign or "CENTER")
text:SetJustifyV(v.valign or "CENTER")
text:SetTextColor(v.text.color.r/256 or 1, v.text.color.g/256 or 1, v.text.color.b/256 or 1, v.text.color.a or 1)
text:SetText(v.text.caption or "")
end
--TODO: set texture
--FrameX.tframes[i]:SetNormalTexture("\\media\\button.tga")
-- if frame is movable
if v.movable and v.movable.enabled then
fxi:SetMovable(true)
fxi:EnableMouse(true)
fxi:RegisterForDrag(unpack(v.movable.drag))
else
fxi:SetMovable(false)
fxi:EnableMouse(false)
end
-- register click events
if v.clicks then
fxi:EnableMouse(true)
fxi:SetPoint("CENTER")
fxi:RegisterForClicks(unpack(v.clicks))
end
-- create scripts
if v.scripts then -- be careful, if you have scripts, you should have a register clicks
for y = 1, #v.scripts do
s = v.scripts[y]
fxi:SetScript(s.event, s.func)
end
end
-- check if this element has children (recursive call)
if v.children then
p = fxi -- set parent for next createframe
fxi.children = ProcessConfigData(v.children, p)
end
end
return fx
end
------------------------------------------------------------
-- entry point function to create the frames
-- Arguments:
-- - addon: Addon table |table|
-- - config: main tframe table |table|
------------------------------------------------------------
function lfx.makeFrames(addon, cfg)
assert(type(addon) == "table", "Invalid argument #1, must be addon table.")
assert(type(cfg) == "table", "Invalid argument #2, must be LibFrameX config table.")
lfx.data[addon] = {} -- initialize calling addon's store for tframe table
lfx.data[addon].tframes = ProcessConfigData(cfg, UIParent) -- create the frame structure (recursive)
end
------------------------------------------------------------
-- Returns a frame by providing the frame's name
-- Arguments:
-- - t: table of tframes |table|
-- - name: string (name of element to be found) |string|
-- Return: - the frame if found |Frame|
-- - false if frame not found or because invalid arguments were passed
------------------------------------------------------------
function lfx.searchFrame(t, name)
local v = {}
for i = 1, #t do -- LINE 271
if t[i] then
v = t[i]
if v.name == name then
return v
end
if v.children then
return lfx.searchFrame(v.children, name)
end
end
end
return v
end
function lfx.findFrame(addon, name)
local data = lfx.data[addon].tframes
return lfx.searchFrame(data, name)
end
------------------------------------------------------------
-- Sets the position of a specific frame by name
-- Arguments:
-- - name: string (name of element to be found) |string|
-- - a: anchor point of element |string| (eg: "TOPLEFT")
-- - r: relative to element |Frame|
-- - rp: relative parent anchor point |string| (eg: "TOPLEFT")
-- - x: x Cartesian value
-- - y: y Cartesian value
-- Return: - true if successful
-- - false if name not found or because invalid arguments were passed
------------------------------------------------------------
function lfx.setPosition(addon, name, pos) -- a, r, rp, x, y
if name and name ~= "" then
assert(pos, "Must provide a valid position point table")
local f = lfx.findFrame(addon, name)
if f then
f:SetPoint(pos.a, pos.r, pos.rp, pos.x, pos.y) -- LINE 537
return true
end
end
return false
end
The call from the addon
GJB = LibStub("AceAddon-3.0"):NewAddon("GarrisonJukeBox", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0", "AceComm-3.0", "AceSerializer-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("GarrisonJukeBox")
local LFX = LibStub("LibFrameX-1.0")
assert(LFX, "LibFrameX-1.0 not loaded!")
GJB.makeFrames = LFX.makeFrames
GJB.setPosition = LFX.setPosition
....
....
Example pos = {x = 5, y = -1, a = "TOPLEFT", r = "NAME_OF_FRAME", rp = "TOPLEFT"},
....
self:makeFrames(musicplayer)
local pos = self.db.profile.musicplayer.pos
self:setPosition("GJBP_BG", pos) -- THIS IS LINE 1119
ERROR
1x ...GarrisonJukeBox\Libs\LibFramex-1.0\LibFrameX-1.0-7.lua:537: attempt to call method 'SetPoint' (a nil value)
...GarrisonJukeBox\Libs\LibFramex-1.0\LibFrameX-1.0-7.lua:537: in function `setPosition'
GarrisonJukeBox\GarrisonJukeBox-v1.0.9.0 release.lua:1119: in function <GarrisonJukeBox\GarrisonJukeBox.lua:1103>
(tail call): ?
If I read right, recursive functions are ok to use in LUA. I tried a test with SciTE (LUA interpreter) and it worked fine.
Your lfx.searchFrame isn't working as intended; the recursion is broken, it may return an empty table and it wont return false in any case.
Try something like this (100% dry coded)
function lfx.searchFrame(t, name)
if type(t) ~= "table" or type(name) ~= "string" return false end -- invalid arguments
for _, f in pairs(t) do
if f.name == name then -- frame found
return f
end
if f.children then
local cf = lfx.searchFrame(v.children, name)
if cf then -- frame found in children
return cf
end
end
end
return false -- frame not found
end
1) There is a "local fx = {}" inside the loop just before assigning the created frame to fx[i].
2) Beware of #t as it only counts the consecutive non-nil entries starting from index 1. E.g.
local t = {1,2,nil,4,5}
print(#t) -- 2
3) When a frame is created with CreateFrame("Frame", name), it is automatically assigned to a global variable of the same name. You can retrieve it by using _G[name] and the frame name can be retrieved by frame:GetName(). However, this can cause conflicts. If two frames have the same name, _G[name] only returns the last-created one. This opens the door to funny, awful-to-debug behaviors, unless you make sure frame names are unique.
The location of the declaration "local fx = {}" was far from practical as the function always returned nil. Thank you for pointing out the obvious. :shock:
2) Beware of #t as it only counts the consecutive non-nil entries starting from index 1. E.g.
local t = {1,2,nil,4,5}
print(#t) -- 2
While it is best to not ever use # on sparse arrays, it isn't actually "consecutive non-nil entries starting from index 1".
local t = {1,2,nil,4,5,nil,6}
print(#t) -- is NOT 2, it's 7
Officially it is "any integer index n such that t[n] is not nil and t[n+1] is nil". Emphasis on "any" (and if you bother to check source, it is first non-nil/nil pair index that binary search happens to find using internal pre-allocated table size as boundary, but of course you can't count on that).
Emphasis on "any" (and if you bother to check source, it is first non-nil/nil pair index that binary search happens to find using internal pre-allocated table size as boundary, but of course you can't count on that).
So it's more or less dependent on the internal implementation with sparse tables.
Rollback Post to RevisionRollBack
To post a comment, please login or register a new account.
My first expectation upon seeing the word library meant a reusable "instance" that held properties of its own. However, after having read more and more into it, it seems that libraries share their properties with all other addons using it; like static members of a class.
Ex:
- MyAddon1 and MyAddon2 both use MyLib1
- MyLib1 has a local table called tf and a function MyLib1:FillTable(t)
- MyAddon1 instantiates ml1 = LibStub:NewLibrary("MyLib1") and calls ml1:FillTable passing a table that looks like this t = {1,2,3}
Here's where it gets interesting...
- MyAddon2 also tries to instantiate MyLib1, however, MyLib1 already exists and its major and minor are valid so no need to upgrade. This I understand and this, I think, has answered my question.
- When MyAddon2 calls ml2:FillTable, with t = {4,5,6}, it overwrites the contents of tf (originally 1,2,3) with 4,5,6.
My question is this.
I can segregate each passed tf from the calling addons into its own index using the addon's name or whatever string the user decides... But won't this be a size issue? (15-20 addons all having their config in LibFrameX???)
Or is there a better solution to this... Maybe the whole idea is evil...
Any thoughts?
Edit: One thought I have is to create a table in the addon, pass it to LibFrameX, have the lib return a fully created frame structure to the addon.
Then it would be the addon responsible to manage it's frames using functionality of the lib. The only downfall to this approach is that I would need to pass tf to every lib function call. >:(
As a bonus, this can be used smartly by embedding the library methods into the addon:
Memory size shouldn't be an issue, as I think there is only one memory space for all addons.
Wouldn't you need to provide the addon index (self) to every function being called in order to access the right node in the lib data table; like all getter/setter functions?
No. Don't get fooled by the colon/method syntactic sugar.
is the same as
which is the same as (because it was assigned to be the same as)
Here's my code.
The call from the addon
ERROR
1x ...GarrisonJukeBox\Libs\LibFramex-1.0\LibFrameX-1.0-7.lua:537: attempt to call method 'SetPoint' (a nil value)
...GarrisonJukeBox\Libs\LibFramex-1.0\LibFrameX-1.0-7.lua:537: in function `setPosition'
GarrisonJukeBox\GarrisonJukeBox-v1.0.9.0 release.lua:1119: in function <GarrisonJukeBox\GarrisonJukeBox.lua:1103>
(tail call): ?
If I read right, recursive functions are ok to use in LUA. I tried a test with SciTE (LUA interpreter) and it worked fine.
Try something like this (100% dry coded)
2) Beware of #t as it only counts the consecutive non-nil entries starting from index 1. E.g.
3) When a frame is created with CreateFrame("Frame", name), it is automatically assigned to a global variable of the same name. You can retrieve it by using _G[name] and the frame name can be retrieved by frame:GetName(). However, this can cause conflicts. If two frames have the same name, _G[name] only returns the last-created one. This opens the door to funny, awful-to-debug behaviors, unless you make sure frame names are unique.
Officially it is "any integer index n such that t[n] is not nil and t[n+1] is nil". Emphasis on "any" (and if you bother to check source, it is first non-nil/nil pair index that binary search happens to find using internal pre-allocated table size as boundary, but of course you can't count on that).
So it's more or less dependent on the internal implementation with sparse tables.