updated libcustommenu

git [04-17-18 - 17:17]
updated libcustommenu
Filename
DailyAutoShare.txt
libs/LibCustomMenu/LibCustomMenu.lua
libs/LibCustomMenu/Unlicense.rtf
diff --git a/DailyAutoShare.txt b/DailyAutoShare.txt
index 3291ebd..3236c01 100644
--- a/DailyAutoShare.txt
+++ b/DailyAutoShare.txt
@@ -1,11 +1,12 @@
 ## Title: DailyAutoShare
 ## Author: manavortex
-## Version: 3.1.0c
+## Version: 3.1.1
 ## APIVersion: 100022
 ## SavedVariables: DAS_Settings DAS_Globals
-## OptionalDependsOn: LibStub LibAddonMenu-2.0 LibMediaProvider-1.0 pchat
+## OptionalDependsOn: LibStub LibCustomMenu LibAddonMenu-2.0 LibMediaProvider-1.0 pchat

 libs\LibStub\LibStub.lua
+libs\LibCustomMenu\LibCustomMenu.lua

 libs\LibAddonMenu-2.0\LibAddonMenu-2.0.lua
 libs\LibAddonMenu-2.0\controls\panel.lua
diff --git a/libs/LibCustomMenu/LibCustomMenu.lua b/libs/LibCustomMenu/LibCustomMenu.lua
new file mode 100644
index 0000000..2164065
--- /dev/null
+++ b/libs/LibCustomMenu/LibCustomMenu.lua
@@ -0,0 +1,646 @@
+-- authors: votan, sirinsidiator
+-- thanks to: baertram & circonian
+
+-- Register with LibStub
+local MAJOR, MINOR = "LibCustomMenu", 6.4
+local lib, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+if not lib then return end -- the same or newer version of this lib is already loaded into memory
+
+local wm = WINDOW_MANAGER
+
+----- Common -----
+local function SetupDivider(pool, control)
+	local function GetTextDimensions(self)
+		return 32, 7
+	end
+	local function Noop(self)
+	end
+
+	local label = wm:CreateControlFromVirtual("$(parent)Name", control, "ZO_BaseTooltipDivider")
+	label:ClearAnchors()
+	label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 2)
+	label:SetAnchor(TOPRIGHT, control, TOPRIGHT, 0, 2)
+	-- First and last time the anchors are set
+	label.ClearAnchors = Noop
+	label.SetAnchor = Noop
+
+	label.SetText = Noop
+	label.SetFont = Noop
+	label.GetTextDimensions = GetTextDimensions
+	label.SetHorizontalAlignment = Noop
+	label:SetHidden(false)
+	control.nameLabel = label
+
+	control:SetMouseEnabled(false)
+end
+
+lib.DIVIDER = "-"
+
+----- Sub Menu -----
+
+local Submenu = ZO_Object:Subclass()
+
+local SUBMENU_ITEM_MOUSE_ENTER = 1
+local SUBMENU_ITEM_MOUSE_EXIT = 2
+local SUBMENU_SHOW_TIMEOUT = 350
+local SUBMENU_HIDE_TIMEOUT = 350
+
+local submenuCallLaterHandle
+local nextId = 1
+local function ClearTimeout()
+	if (submenuCallLaterHandle ~= nil) then
+		EVENT_MANAGER:UnregisterForUpdate(submenuCallLaterHandle)
+		submenuCallLaterHandle = nil
+	end
+end
+
+local function SetTimeout(callback)
+	if (submenuCallLaterHandle ~= nil) then ClearTimeout() end
+	submenuCallLaterHandle = "LibCustomMenuSubMenuTimeout" .. nextId
+	nextId = nextId + 1
+
+	EVENT_MANAGER:RegisterForUpdate(submenuCallLaterHandle, SUBMENU_SHOW_TIMEOUT, function()
+		ClearTimeout()
+		if callback then callback() end
+	end )
+end
+
+local function GetValueOrCallback(arg, ...)
+	if type(arg) == "function" then
+		return arg(...)
+	else
+		return arg
+	end
+end
+
+function Submenu:New(...)
+	local object = ZO_Object.New(self)
+	object:Initialize(...)
+	return object
+end
+
+function Submenu:Initialize(name)
+	self.window = ZO_Menus
+
+	local submenuControl = self.window:CreateControl(name, CT_CONTROL)
+	submenuControl:SetClampedToScreen(true)
+	submenuControl:SetMouseEnabled(true)
+	submenuControl:SetHidden(true)
+	-- OnMouseEnter: Stop hiding of submenu initiated by mouse exit of parent
+	submenuControl:SetHandler("OnMouseEnter", ClearTimeout)
+
+	local function ExitSubMenu() if self.parent and self.parent.OnSelect then self.parent:OnSelect(SUBMENU_ITEM_MOUSE_EXIT) end end
+	submenuControl:SetHandler("OnMouseExit", function(control) SetTimeout(ExitSubMenu) end)
+
+	submenuControl:SetHandler("OnHide", function(control) ClearTimeout() self:Clear() end)
+	submenuControl:SetDrawLevel(ZO_Menu:GetDrawLevel() + 1)
+
+	local bg = submenuControl:CreateControl("$(parent)BG", CT_BACKDROP)
+	-- bg:SetCenterColor(0, 0, 0, .93)
+	bg:SetCenterTexture("EsoUI/Art/Tooltips/UI-TooltipCenter.dds")
+	bg:SetEdgeTexture("EsoUI/Art/Tooltips/UI-Border.dds", 128, 16)
+	bg:SetInsets(16, 16, -16, -16)
+	bg:SetAnchorFill()
+
+	local overlay = bg:CreateControl("$(parent)MungeOverlay", CT_TEXTURE)
+	overlay:SetTexture("EsoUI/Art/Tooltips/munge_overlay.dds")
+	overlay:SetAddressMode(TEX_MODE_WRAP)
+	overlay:SetAnchor(TOPLEFT)
+	overlay:SetAnchor(BOTTOMRIGHT)
+
+	self.highlight = CreateControlFromVirtual("$(parent)Highlight", submenuControl, "ZO_SelectionHighlight")
+	self.highlight:SetHidden(true)
+
+	self.control = submenuControl
+
+	local upInside = false
+	local function MouseEnter(control)
+		upInside = true
+		ClearTimeout()
+		self:SetSelectedIndex(control.index)
+	end
+	local function MouseExit(control)
+		upInside = false
+		if (self.selectedIndex == control.index) then
+			self:SetSelectedIndex(nil)
+		end
+	end
+	local function MouseUp(control, button)
+		if upInside == true and button == MOUSE_BUTTON_INDEX_LEFT then
+			ZO_Menu_SetLastCommandWasFromMenu(true)
+			if control.checkbox then
+				-- The checkbox click handler will handle it
+				ZO_CheckButton_OnClicked(control.checkbox, button)
+			else
+				if not control.OnSelect() then
+					ClearMenu()
+				end
+			end
+		end
+	end
+
+	local function ItemFactory(pool)
+		local control = CreateControlFromVirtual("ZO_SubMenuItem", submenuControl, "ZO_MenuItem", pool:GetNextControlId())
+		control.nameLabel = GetControl(control, "Name")
+
+		control:SetHandler("OnMouseEnter", MouseEnter)
+		control:SetHandler("OnMouseExit", MouseExit)
+		control:SetHandler("OnMouseDown", IgnoreMouseDownEditFocusLoss)
+		control:SetHandler("OnMouseUp", MouseUp)
+
+		return control
+	end
+
+	local function ResetFunction(control)
+		control:SetHidden(true)
+		control:ClearAnchors()
+		control.OnSelect = nil
+		control.menuIndex = nil
+	end
+
+	local function DividerFactory(pool)
+		local control = CreateControlFromVirtual("ZO_CustomSubMenuDivider", submenuControl, "ZO_NotificationsRowButton", pool:GetNextControlId())
+		SetupDivider(pool, control)
+		return control
+	end
+
+	local function ResetCheckbox(checkbox)
+		ResetFunction(checkbox)
+	end
+
+	local function CheckBoxMouseEnter(control)
+		MouseEnter(control:GetParent())
+	end
+	local function CheckBoxMouseExit(control)
+		MouseExit(control:GetParent())
+	end
+	local function CheckBoxMouseUp(control)
+		self.refCount =(self.refCount or 0) + 1
+		local parent = control:GetParent()
+		parent.OnSelect(ZO_CheckButton_IsChecked(control))
+	end
+	local function CheckBoxFactory(pool)
+		local control = CreateControlFromVirtual("ZO_CustomSubMenuItemCheckButton", submenuControl, "ZO_CheckButton", pool:GetNextControlId())
+		control.nameLabel = control
+
+		control:SetHandler("OnMouseEnter", CheckBoxMouseEnter)
+		control:SetHandler("OnMouseExit", CheckBoxMouseExit)
+
+		ZO_CheckButton_SetToggleFunction(control, CheckBoxMouseUp)
+
+		return control
+	end
+
+
+	self.itemPool = ZO_ObjectPool:New(ItemFactory, ResetFunction)
+	self.dividerPool = ZO_ObjectPool:New(DividerFactory, ResetFunction)
+	self.checkBoxPool = ZO_ObjectPool:New(CheckBoxFactory, ResetCheckbox)
+	self.items = { }
+
+	EVENT_MANAGER:RegisterForEvent(name .. "_OnGlobalMouseUp", EVENT_GLOBAL_MOUSE_UP, function()
+		if self.refCount ~= nil then
+			local moc = wm:GetMouseOverControl()
+			if (moc:GetOwningWindow() ~= submenuControl) then
+				self.refCount = self.refCount - 1
+				if self.refCount <= 0 then
+					self:Clear()
+				end
+			end
+		end
+	end )
+end
+
+function Submenu:SetSelectedIndex(index)
+	if (index) then
+		index = zo_max(zo_min(index, #self.items), 1)
+	end
+
+	if (self.selectedIndex ~= index) then
+		self:UnselectItem(self.selectedIndex)
+		self:SelectItem(index)
+	end
+end
+
+function Submenu:UnselectItem(index)
+	local item = self.items[index]
+	if item then
+		self.highlight:SetHidden(true)
+		local nameControl = item.nameLabel
+		nameControl:SetColor(nameControl.normalColor:UnpackRGBA())
+
+		self.selectedIndex = nil
+	end
+end
+
+function Submenu:SelectItem(index)
+	local item = self.items[index]
+	if item then
+		local highlight = self.highlight
+
+		highlight:ClearAnchors()
+
+		highlight:SetAnchor(TOPLEFT, item, TOPLEFT, -2, -2)
+		highlight:SetAnchor(BOTTOMRIGHT, item, BOTTOMRIGHT, 2, 2)
+
+		highlight:SetHidden(false)
+
+		local nameControl = item.nameLabel
+		nameControl:SetColor(nameControl.highlightColor:UnpackRGBA())
+
+		self.selectedIndex = index
+	end
+end
+
+function Submenu:UpdateAnchors()
+	local iconSize = self.iconSize
+	local previousItem = self.control
+	local items = self.items
+	local width, height = 0, 0
+	local padding = ZO_Menu.menuPad
+
+	for i = 1, #items do
+		local item = items[i]
+		local textWidth, textHeight = item.nameLabel:GetTextDimensions()
+		width = math.max(textWidth + padding * 2, width)
+		height = height + textHeight
+		item:ClearAnchors()
+		if i == 1 then
+			item:SetAnchor(TOPLEFT, previousItem, TOPLEFT, padding, padding)
+			item:SetAnchor(TOPRIGHT, previousItem, TOPRIGHT, - padding, padding)
+		else
+			item:SetAnchor(TOPLEFT, previousItem, BOTTOMLEFT, 0, item.itemYPad)
+			item:SetAnchor(TOPRIGHT, previousItem, BOTTOMRIGHT, 0, item.itemYPad)
+		end
+
+		item:SetHidden(false)
+		item:SetDimensions(textWidth, textHeight)
+		previousItem = item
+	end
+
+	self.control:SetDimensions(width + padding * 2, height + padding * 2)
+end
+
+function Submenu:Clear()
+	self:UnselectItem(self.selectedIndex)
+	self.items = { }
+	self.itemPool:ReleaseAllObjects()
+	self.dividerPool:ReleaseAllObjects()
+	self.checkBoxPool:ReleaseAllObjects()
+	self.control:SetHidden(true)
+	self.refCount = nil
+end
+
+local DEFAULT_TEXT_COLOR = ZO_ColorDef:New(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_NORMAL))
+local DEFAULT_TEXT_HIGHLIGHT = ZO_ColorDef:New(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_CONTEXT_HIGHLIGHT))
+
+function Submenu:AddItem(entry, myfont, normalColor, highlightColor, itemYPad)
+	local visible
+	if entry.visible ~= nil then visible = entry.visible else visible = true end
+	if not GetValueOrCallback(visible, ZO_Menu) then return end
+
+	local item, key
+	local itemType = entry.itemType or MENU_ADD_OPTION_LABEL
+	if itemType == MENU_ADD_OPTION_LABEL then
+		item, key = entry.label ~= lib.DIVIDER and self.itemPool:AcquireObject() or self.dividerPool:AcquireObject()
+	elseif itemType == MENU_ADD_OPTION_CHECKBOX then
+		item, key = self.itemPool:AcquireObject()
+	else
+		error(string.format("Unknown menu entry itemType: %s", itemType))
+	end
+
+	item.OnSelect = entry.callback
+	item.index = #self.items + 1
+	self.items[item.index] = item
+
+	local nameControl = item.nameLabel
+
+	local entryFont = GetValueOrCallback(entry.myfont, ZO_Menu, item) or myfont
+	local normColor = GetValueOrCallback(entry.normalColor, ZO_Menu, item) or normalColor
+	local highColor = GetValueOrCallback(entry.highlightColor, ZO_Menu, item) or highlightColor
+	myfont = entryFont or "ZoFontGame"
+	nameControl.normalColor = normColor or DEFAULT_TEXT_COLOR
+	nameControl.highlightColor = highColor or DEFAULT_TEXT_HIGHLIGHT
+
+	nameControl:SetFont(myfont)
+
+	local text = GetValueOrCallback(entry.label, ZO_Menu, item)
+
+	local checkboxItemControl = nil
+	if itemType == MENU_ADD_OPTION_CHECKBOX then
+		checkboxItemControl = self.checkBoxPool:AcquireObject()
+		checkboxItemControl:SetParent(item)
+		checkboxItemControl.menuIndex = item.index
+		checkboxItemControl:ClearAnchors()
+		checkboxItemControl:SetHidden(false)
+		checkboxItemControl:SetAnchor(LEFT, nil, LEFT, 2, -1)
+		text = string.format(" |u18:0::|u%s", text)
+		ZO_CheckButton_SetCheckState(checkboxItemControl, GetValueOrCallback(entry.checked, ZO_Menu, item) or false)
+	end
+	item.checkbox = checkboxItemControl
+
+	nameControl:SetText(text)
+
+	local enabled = not GetValueOrCallback(entry.disabled or false, ZO_Menu, item)
+	nameControl:SetColor((enabled and nameControl.normalColor or ZO_DEFAULT_DISABLED_COLOR):UnpackRGBA())
+	item:SetMouseEnabled(enabled)
+end
+
+function Submenu:Show(parent)
+	if not self.control:IsHidden() then self:Clear() return false end
+	self:UpdateAnchors()
+
+	local padding = ZO_Menu.menuPad
+	local control = self.control
+	control:ClearAnchors()
+	-- If there is not enough space on the right side, use the left side. Like Windows.
+	if (parent:GetRight() + control:GetWidth()) < GuiRoot:GetRight() then
+		control:SetAnchor(TOPLEFT, parent, TOPRIGHT, -1, - padding)
+	else
+		control:SetAnchor(TOPRIGHT, parent, TOPLEFT, 1, - padding)
+	end
+	control:SetHidden(false)
+	self.parent = parent
+	self.refCount = 2
+
+	return true
+end
+
+local function SubMenuItemFactory(pool)
+	local control = CreateControlFromVirtual("ZO_CustomSubMenuItem", ZO_Menu, "ZO_NotificationsRowButton", pool:GetNextControlId())
+
+	local arrowContainer = control:CreateControl("$(parent)Arrow", CT_CONTROL)
+	-- we need this in order to control the menu with independently of the texture size
+	arrowContainer:SetAnchor(RIGHT, control, RIGHT, 0, 0)
+	arrowContainer:SetDimensions(32, 16)
+
+	local arrow = arrowContainer:CreateControl("$(parent)Texture", CT_TEXTURE)
+	arrow:SetAnchor(RIGHT, arrowContainer, RIGHT, 0, 0)
+	arrow:SetDimensions(16, 20)
+	arrow:SetTexture("EsoUI/Art/Miscellaneous/colorPicker_slider_vertical.dds")
+	arrow:SetTextureCoords(0, 0.5, 0, 1)
+
+	-- we assign the submenu arrow to checkbox because the context menu will add the desired width automatically that way
+	control.checkbox = arrowContainer
+
+	local clicked = false
+	local function MouseEnter(control)
+		ZO_Menu_EnterItem(control)
+		clicked = false
+		SetTimeout( function() if control.OnSelect then control:OnSelect(SUBMENU_ITEM_MOUSE_ENTER) end end)
+	end
+	local function MouseExit(control)
+		ZO_Menu_ExitItem(control)
+		if not clicked then
+			SetTimeout( function() if control.OnSelect then control:OnSelect(SUBMENU_ITEM_MOUSE_EXIT) end end)
+		end
+	end
+	local function MouseDown(control)
+		IgnoreMouseDownEditFocusLoss()
+		-- re-open sub menu on click
+		clicked = true
+		control:OnSelect(SUBMENU_ITEM_MOUSE_ENTER)
+	end
+
+	local label = wm:CreateControl("$(parent)Name", control, CT_LABEL)
+	label:SetAnchor(TOPLEFT)
+	control.nameLabel = label
+
+	control:SetHandler("OnMouseEnter", MouseEnter)
+	control:SetHandler("OnMouseExit", MouseExit)
+	control:SetHandler("OnMouseDown", MouseDown)
+
+	return control
+end
+
+----- Standard Menu -----
+
+local function ResetMenuItem(button)
+	button:SetHidden(true)
+	button:ClearAnchors()
+	button.menuIndex = nil
+	button.OnSelect = nil
+end
+
+local function ResetCheckBox(checkBox)
+	ResetMenuItem(checkBox)
+	ZO_CheckButton_SetToggleFunction(checkBox, nil)
+end
+
+local upInside = false
+
+local function MenuItemFactory(pool)
+	local control = CreateControlFromVirtual("ZO_CustomMenuItem", ZO_Menu, "ZO_NotificationsRowButton", pool:GetNextControlId())
+	local function MouseEnter()
+		upInside = true
+		ZO_Menu_EnterItem(control)
+	end
+	local function MouseExit()
+		upInside = false
+		ZO_Menu_ExitItem(control)
+	end
+	local function MouseUp()
+		if upInside == true then
+			ZO_Menu_ClickItem(control, 1)
+		end
+	end
+
+	local label = wm:CreateControl("$(parent)Name", control, CT_LABEL)
+	label:SetAnchor(TOPLEFT)
+	control.nameLabel = label
+
+	control:SetHandler("OnMouseEnter", MouseEnter)
+	control:SetHandler("OnMouseExit", MouseExit)
+	control:SetHandler("OnMouseDown", IgnoreMouseDownEditFocusLoss)
+	control:SetHandler("OnMouseUp", MouseUp)
+
+	return control
+end
+
+local function CheckBoxFactory(pool)
+	local control = CreateControlFromVirtual("ZO_CustomMenuItemCheckButton", ZO_Menu, "ZO_CheckButton", pool:GetNextControlId())
+	control.nameLabel = control
+
+	local function MouseEnter()
+		ZO_Menu_EnterItem(control)
+	end
+	local function MouseExit()
+		ZO_Menu_ExitItem(control)
+	end
+	control:SetHandler("OnMouseEnter", MouseEnter)
+	control:SetHandler("OnMouseExit", MouseExit)
+	return control
+end
+
+local function DividerFactory(pool)
+	local control = CreateControlFromVirtual("ZO_CustomMenuDivider", ZO_Menu, "ZO_NotificationsRowButton", pool:GetNextControlId())
+	SetupDivider(pool, control)
+	return control
+end
+
+---- Hook points for context menu -----
+
+local function HookContextMenu()
+	local category, registry, inventorySlot, slotActions, entered
+	local function Reset()
+		category, registry, inventorySlot, slotActions = 0, nil, nil, nil
+	end
+	local function RemoveMouseOverKeybinds()
+		if entered then
+			entered = false
+			lib.keybindRegistry:FireCallbacks("Exit")
+		end
+		Reset()
+	end
+	local function addCategory()
+		category = category + 1
+		registry:FireCallbacks(category, inventorySlot, slotActions)
+	end
+	local function AddSlots(...)
+		Reset()
+		inventorySlot, slotActions = ...
+		if slotActions.m_contextMenuMode then
+			registry = lib.contextMenuRegistry
+		else
+			entered = true
+			registry = lib.keybindRegistry
+		end
+	end
+	local function InsertToMenu()
+		if category < 4 and inventorySlot then
+			addCategory()
+		end
+	end
+	local function AppendToMenu()
+		if registry then
+			if inventorySlot then
+				while category <= 6 do addCategory() end
+			end
+			Reset()
+		end
+	end
+	Reset()
+
+	ZO_PreHook("ZO_InventorySlot_RemoveMouseOverKeybinds", RemoveMouseOverKeybinds)
+	ZO_PreHook("ZO_InventorySlot_OnMouseExit", RemoveMouseOverKeybinds)
+	ZO_PreHook("ZO_InventorySlot_DiscoverSlotActionsFromActionList", AddSlots)
+	ZO_PreHook(ZO_InventorySlotActions, "AddSlotAction", InsertToMenu)
+	ZO_PreHook(ZO_InventorySlotActions, "Show", AppendToMenu)
+	ZO_PreHook(ZO_InventorySlotActions, "GetPrimaryActionName", AppendToMenu)
+end
+
+----- Public API -----
+
+function AddCustomMenuItem(mytext, myfunction, itemType, myFont, normalColor, highlightColor, itemYPad, horizontalAlignment)
+	local orgItemPool = ZO_Menu.itemPool
+	local orgCheckboxItemPool = ZO_Menu.checkBoxPool
+
+	ZO_Menu.itemPool = mytext ~= lib.DIVIDER and lib.itemPool or lib.dividerPool
+	ZO_Menu.checkBoxPool = lib.checkBoxPool
+
+	local index = AddMenuItem(mytext, myfunction, itemType, myFont, normalColor, highlightColor, itemYPad, horizontalAlignment)
+
+	ZO_Menu.itemPool = orgItemPool
+	ZO_Menu.checkBoxPool = orgCheckboxItemPool
+
+	return index
+end
+
+function AddCustomSubMenuItem(mytext, entries, myfont, normalColor, highlightColor, itemYPad)
+	local function CreateSubMenu(control, state)
+		if (state == SUBMENU_ITEM_MOUSE_ENTER) then
+			lib.submenu:Clear()
+			local currentEntries = GetValueOrCallback(entries, ZO_Menu, control)
+			local entry
+			for i = 1, #currentEntries do
+				entry = currentEntries[i]
+				lib.submenu:AddItem(entry, myfont, normalColor, highlightColor, itemYPad)
+			end
+			lib.submenu:Show(control)
+		elseif (state == SUBMENU_ITEM_MOUSE_EXIT) then
+			lib.submenu:Clear()
+		end
+	end
+
+	local orgItemPool = ZO_Menu.itemPool
+	local orgCheckboxItemPool = ZO_Menu.checkBoxPool
+
+	ZO_Menu.itemPool = lib.submenuPool
+	ZO_Menu.checkBoxPool = lib.checkBoxPool
+
+	local index = AddMenuItem(mytext, CreateSubMenu, MENU_ADD_OPTION_LABEL, myfont, normalColor, highlightColor, itemYPad)
+
+	ZO_Menu.itemPool = orgItemPool
+	ZO_Menu.checkBoxPool = orgCheckboxItemPool
+
+	return index
+end
+
+local function HookClearMenu()
+	local orgClearMenu = ClearMenu
+	function ClearMenu()
+		ClearTimeout()
+		orgClearMenu()
+		lib.itemPool:ReleaseAllObjects()
+		lib.submenuPool:ReleaseAllObjects()
+		lib.checkBoxPool:ReleaseAllObjects()
+		lib.dividerPool:ReleaseAllObjects()
+		lib.submenu:Clear()
+	end
+end
+
+local function HookAddSlotAction()
+	function ZO_InventorySlotActions:AddCustomSlotAction(...)
+		local orgItemPool = ZO_Menu.itemPool
+		local orgCheckboxItemPool = ZO_Menu.checkBoxPool
+
+		ZO_Menu.itemPool = lib.itemPool
+		ZO_Menu.checkBoxPool = lib.checkBoxPool
+
+		self:AddSlotAction(...)
+
+		ZO_Menu.itemPool = orgItemPool
+		ZO_Menu.checkBoxPool = orgCheckboxItemPool
+	end
+end
+
+function lib:RegisterContextMenu(func, category, ...)
+	category = zo_clamp(category or self.CATEGORY_LATE, self.CATEGORY_EARLY, self.CATEGORY_LATE)
+	self.contextMenuRegistry:RegisterCallback(category, func, ...)
+end
+
+function lib:RegisterKeyStripEnter(func, category, ...)
+	category = zo_clamp(category or self.CATEGORY_LATE, self.CATEGORY_EARLY, self.CATEGORY_LATE)
+	self.keybindRegistry:RegisterCallback(category, func, ...)
+end
+
+function lib:RegisterKeyStripExit(func, ...)
+	self.keybindRegistry:RegisterCallback("Exit", func, ...)
+end
+
+---- Init -----
+
+local function OnAddonLoaded(event, name)
+	if name:find("^ZO_") then return end
+	EVENT_MANAGER:UnregisterForEvent(MAJOR, EVENT_ADD_ON_LOADED)
+	lib.itemPool = ZO_ObjectPool:New(MenuItemFactory, ResetMenuItem)
+	lib.submenuPool = ZO_ObjectPool:New(SubMenuItemFactory, ResetMenuItem)
+	lib.checkBoxPool = ZO_ObjectPool:New(CheckBoxFactory, ResetCheckBox)
+	lib.dividerPool = ZO_ObjectPool:New(DividerFactory, ResetMenuItem)
+	lib.submenu = Submenu:New("LibCustomMenuSubmenu")
+	HookClearMenu()
+	HookAddSlotAction()
+	HookContextMenu()
+end
+
+lib.contextMenuRegistry = lib.contextMenuRegistry or ZO_CallbackObject:New()
+lib.keybindRegistry = lib.keybindRegistry or ZO_CallbackObject:New()
+
+lib.CATEGORY_EARLY = 1
+lib.CATEGORY_PRIMARY = 2
+lib.CATEGORY_SECONDARY = 3
+lib.CATEGORY_TERTIARY = 4
+lib.CATEGORY_QUATERNARY = 5
+lib.CATEGORY_LATE = 6
+
+EVENT_MANAGER:UnregisterForEvent(MAJOR, EVENT_ADD_ON_LOADED)
+EVENT_MANAGER:RegisterForEvent(MAJOR, EVENT_ADD_ON_LOADED, OnAddonLoaded)
diff --git a/libs/LibCustomMenu/Unlicense.rtf b/libs/LibCustomMenu/Unlicense.rtf
new file mode 100644
index 0000000..68a49da
--- /dev/null
+++ b/libs/LibCustomMenu/Unlicense.rtf
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>