Info
This library is for
addon developers. Download it, if an addon dependency tells you so.
Description
This library is written to overcome one way to get the "Access a private function XYZ from insecure code". But beginning with version 2.0, it does additional provide a new feature: sub menus.
Beginning with version 3.0, it does additional provide a new feature: divider.
Background
Controls, created from add-on code (as part of code path) are marked as "insecure/compromissed".
Functions, which have no problem with been called from "insecure" controls, are still working perfectly.
Like those of add-ons or
Show On Map,
Link in Chat or
Get Help.
But "secured" functions like
UseItem,
InitiateDestroy,
PickupInventoryItem raising the error message from above.
Once you hook
AddMenuItem ALL controls created for the context-menu are "insecure".
Prior to ESO 2.0.13 if an add-on offers a full custom context-menu (no built-in menu entries) and this context-menu is shown first after (re-)load UI the first menu item controls are insecure. A crash of "Use" in the inventory afterwards was guaranteed.
Starting with ESO 2.0.13 ZOS preallocates 10 "secure" menu items. See
here.
But this just reduces the chance of running into that problem, it does not fix it.
Currently the number of preallocated controls is 30. Running into this problem with AddMenuItem is real rare, but inventory action slots still don't like custom menu entries.
To avoid the error message, the controls of built-in menu items and add-on menu items must be strictly separated. That's what
AddCustomMenuItem of this library does. It uses an own pool of controls, which look exactly the same. Sounds strange, but works.
I don't use private functions. Why should I use this lib?
It's not you, who uses private functions. It is built-in code, which re-uses controls indirectly created by your add-on in AddMenuItem.
I want to use this lib, so what to do?
After you have included the lib in your add-on manifest (.txt) do a text search for the global function AddMenuItem (not any :AddMenuItem of other objects) and replace it with AddCustomMenuItem.
Be careful with a simple "Replace All" over all files! You probably replace the AddMenuItem of LibCustomMenu itself
Version 1.0
This version was intended as proof of concept, but was successfully used in Beartram's FCO Item Saver, Circonian's FilterIt and my Fish Fillet.
Version 2.0
In order to have more value than avoiding a rare, just annoying "bug", sirinsidiator suggested and provided proof of concept code for sub menu items.
A big thank to sirinsidiator!
I finalized it and here we are.
API 2.0
function AddCustomMenuItem(mytext, myfunction, itemType, myfont, normalColor, highlightColor, itemYPad)
Fully compatible with AddMenuItem.
mytext: string, required. Caption of menu item.
myfunction: function(), required. Called if clicked.
itemType: int, optional. MENU_ADD_OPTION_LABEL or MENU_ADD_OPTION_CHECKBOX. Default MENU_ADD_OPTION_LABEL.
myfont: string, optional.
normalColor: ZO_ColorDef, optional. Color of unselected item.
highlightColor: ZO_ColorDef, optional. Color of selected/hovered item.
itemYPad: int, optional. y-padding between items.
function AddCustomSubMenuItem(mytext, entries, myfont, normalColor, highlightColor, itemYPad, subMenuButtonCallbackFunc)
mytext: string, required. Caption of menu item.
entries: table of sub items or callback returning table of sub items, required.
myfont: string, optional.
normalColor: ZO_ColorDef, optional. Color of unselected/normal sub item.
highlightColor: ZO_ColorDef, optional. Color of selected/hovered sub item.
itemYPad: int, optional. y-padding between sub items.
subMenuButtonCallbackFunc: function, optional. Callback function for the click on the submenu open button (the one at the main menu showing the submenu)
sub item:
label: string or function(rootMenu, childControl), required.
callback: function(), required.
disabled: boolean or function(rootMenu, childControl), optional. Default false. if true, sub item is visible, but gray and not clickable.
visible: boolean or function(rootMenu, childControl), optional. Default true.
These examples are for self-created menus. If you want to add items to the inventory context-menu, look at the example for LibCustomMenu:RegisterContextMenu more below.
example 1:
Lua Code:
local entries = {
{
label = "Test 1",
callback = function() d("Test 1") end,
},
{
label = "Test 2",
callback = function() d("Test 2") end,
disabled = function(rootMenu, childControl) return true end,
}
}
ClearMenu()
AddCustomSubMenuItem("Sub Menu", entries)
ShowMenu()
example 2:
Lua Code:
local function GetEntries(rootMenu)
d("run")
return {
{
label = function() return GetTimeStamp() end,
callback = function() d("Test 1") end,
},
{
label = "Test 2",
callback = function() d("Test 2") end,
disabled = function(rootMenu, childControl) return true end,
}
}
end
ClearMenu()
AddCustomSubMenuItem("Sub Menu", GetEntries)
ShowMenu()
If you have Notebook or ZAM Notebook, you could copy&paste the scripts from above and execute them.
API 3.0
In addition to API 2.0:
Allow divider by setting member
label to a static "-". Suggested by Beartram.
example:
Lua Code:
local entries = {
{
label = "Test 1",
callback = function() d("Test 1") end,
},
{
label = "-",
},
{
label = "Test 2",
callback = function() d("Test 2") end,
disabled = function(rootMenu, childControl) return true end,
}
}
ClearMenu()
AddCustomSubMenuItem("Sub Menu", entries)
ShowMenu()
API 4.1
In addition to API 3.0:
actionSlots:AddCustomSlotAction(actionStringId, actionCallback, actionType, visibilityFunction, options)
for example while hooking ZO_InventorySlot_DiscoverSlotActionsFromActionList(inventorySlot, slotActions)
for example within the callback of API 6.0. See below.
API 5.0
In addition to API 4.1+:
New entry properties
itemType and
checked.
itemType = MENU_ADD_OPTION_LABEL (default) or MENU_ADD_OPTION_CHECKBOX for a checkbox
checked = false/true or function() return state end
The initial checked state than opening the sub menu.
example:
Lua Code:
local myState = true
local entries = {
{
label = "Test 1",
callback = function(state) myState = state df("Test 1: %s", tostring(myState)) end,
checked = function() return myState end,
itemType = MENU_ADD_OPTION_CHECKBOX,
},
{
label = "Test 1b",
callback = function() d("Test 1b") end,
itemType = MENU_ADD_OPTION_LABEL,
},
{
label = "-",
},
{
label = "Test 2",
callback = function() d("Test 2") end,
disabled = function(rootMenu, childControl) return true end,
}
}
ClearMenu()
AddCustomSubMenuItem("Sub Menu", entries)
ShowMenu()
API 6.2
In addition to API 5+:
Added callbacks, you can register to, to hook into inventory slot context menu. You don't need to reinvent the hook and are able to control the position of your entry/entries more granular.
category
lib.CATEGORY_EARLY
lib.CATEGORY_PRIMARY
lib.CATEGORY_SECONDARY
lib.CATEGORY_TERTIARY
lib.CATEGORY_QUATERNARY
lib.CATEGORY_LATE
CATEGORY_EARLY is before the first built-in menu entry.
CATEGORY_PRIMARY is after the first built-in menu entry. And so on.
CATEGORY_LATE is after built-in menu and default.
lib:RegisterContextMenu(func, category)
Register to the context menu of the inventory mouse right click.
lib:RegisterKeyStripEnter(func, category)
Register to the inventory mouse hover used to update the keybind buttons at the bottom.
func: callback function to be called.
Signature:
Lua Code:
local function func(inventorySlot, slotActions)
end
category: optional. defaults to CATEGORY_LATE.
lib:RegisterKeyStripExit(func)
Register to the inventory mouse hover used to update the keybind buttons at the bottom, if the mouse exits an inventory slot.
func: callback function to be called.
Signature:
example:
Lua Code:
ZO_CreateStringId("SI_BINDING_NAME_SHOW_POPUP", "Show in Popup")
local function AddItem(inventorySlot, slotActions)
local valid = ZO_Inventory_GetBagAndIndex(inventorySlot)
if not valid then return end
slotActions:AddCustomSlotAction(SI_BINDING_NAME_SHOW_POPUP, function()
local bagId, slotIndex = ZO_Inventory_GetBagAndIndex(inventorySlot)
local itemLink = GetItemLink(bagId, slotIndex)
ZO_PopupTooltip_SetLink(itemLink)
end , "")
end
LibCustomMenu:RegisterContextMenu(AddItem, LibCustomMenu.CATEGORY_PRIMARY)
example 2:
Lua Code:
local function AddItem(inventorySlot, slotActions)
local bagId, slotIndex = ZO_Inventory_GetBagAndIndex(inventorySlot)
if not CanItemBePlayerLocked(bagId, slotIndex) then return end
local locked = IsItemPlayerLocked(bagId, slotIndex)
slotActions:AddCustomSlotAction(locked and SI_ITEM_ACTION_UNMARK_AS_LOCKED or SI_ITEM_ACTION_MARK_AS_LOCKED, function()
SetItemIsPlayerLocked(bagId, slotIndex, not locked)
end, "keybind2")
-- you can use: "primary", "secondary", "keybind1", "keybind2"
end
local menu = LibCustomMenu
--menu:RegisterContextMenu(AddItem, menu.CATEGORY_PRIMARY)
menu:RegisterKeyStripEnter(AddItem, menu.CATEGORY_LATE)
Not really practical, because it hides the built-in keybind.
But you could use the callback just to be notified as well.
API 6.8
In addition to API 6.2+:
Added functionality to add an optional tooltip to a menu entry.
For the top level menu entries there is a new global function:
function AddCustomMenuTooltip(tooltip, index)
tooltip: Either a string shown as a simple tooltip or a callback function to let you do everything.
Signature:
Lua Code:
local function func(control, inside)
end
control: the menu entry control.
inside: The function is called on mouse enter with inside=true and on mouse exit with inside=false.
index: Optional. Index of the menu entry, the tooltip is for. By default the index of the last added item is used. => You call AddCustomMenuItem and when AddCustomMenuTooltip.
For sub-menus a new key "tooltip" can be used. Again it is either a string or the callback function with the signature from above.
example:
Lua Code:
local myState = true
local entries = {
{
label = "Test 1",
callback = function(state) myState = state df("Test 1: %s", tostring(myState)) end,
checked = function() return myState end,
itemType = MENU_ADD_OPTION_CHECKBOX,
tooltip = "This is Test 1",
},
{
label = "Test 1b",
callback = function() d("Test 1b") end,
itemType = MENU_ADD_OPTION_LABEL,
tooltip = "This is Test 2",
},
{
label = "-",
},
{
label = "Test 2",
callback = function() d("Test 2") end,
disabled = function(rootMenu, childControl) return true end,
}
}
ClearMenu()
AddCustomSubMenuItem("Sub Menu", entries)
AddCustomMenuTooltip("A sub-menu")
AddCustomMenuItem("-", function() d("soso") end)
AddCustomMenuItem("Button", function() d("jojo") end)
AddCustomMenuTooltip(function(control, inside) if inside then d("A great button") end end)
AddCustomMenuItem("CheckBox", function() d("soso") end, MENU_ADD_OPTION_CHECKBOX)
ShowMenu()
How to use Checkbox at top level
Lua Code:
local index = AddCustomMenuItem("CheckBox", function() <your callback> end, MENU_ADD_OPTION_CHECKBOX)
if needToCheckIt then
ZO_CheckButton_SetChecked(ZO_Menu.items[index].checkbox)
end
API 6.9
lib:EnableSpecialKeyContextMenu(key)
key: KEY_CTRL or KEY_ALT or KEY_SHIFT or KEY_COMMAND
Show an alternative context menu, if the special key is pressed while right-clicking the inventory item.
lib:RegisterSpecialKeyContextMenu(func)
Register a callback for the alternative context menu. You, the addon author, have to check which menu items you want to add for the given combination of special keys. (See signature below)
You have to enable all the keys you want to handle. See lib:EnableSpecialKeyContextMenu(key). There is no DisableSpecialKeyContextMenu, because you don't know who else had enabled them.
Signature:
Lua Code:
local function func(inventorySlot, slotActions, ctrl, alt, shift, command)
end
API 6.92
lib:RegisterPlayerContextMenu(func, category)
Register to the context menu of the chat player link mouse right click,
func: callback function to be called.
Signature:
Code:
local function func(playerName, rawName)
end
Category: See API 6.2 description for the available categories.
API 7.1
lib:RegisterGuildRosterContextMenu(func, category)
Register to the context menu of the guild roster member mouse right click.
func: callback function to be called.
Signature:
Code:
local function func(rowData)
end
API 7.2
lib:RegisterFriendsListContextMenu(func, category)
Register to the context menu of the friends list mouse right click.
lib:RegisterGroupListContextMenu(func, category)
Register to the context menu of the group list mouse right click.
Both analog to RegisterGuildRosterContextMenu. See above.
Example
Lua Code:
local function AddItem(data)
AddCustomMenuItem("Example", function() d(data.displayName) end)
end
local menu = LibCustomMenu
menu:RegisterFriendsListContextMenu(AddItem, menu.CATEGORY_EARLY)
menu:RegisterFriendsListContextMenu(AddItem, menu.CATEGORY_LAST)