Probably the issue most complained about that I can recall was the VFL popup menus not being "backtrackable" (once you had selected a submenu you could not go back to previous ones) and having to press escape to clear excess dangling menus from the screen.
This patch fixes both of these issues. The popup menus now have a mouseover timer and will automatically disappear if the mouse leaves the menu area for more than two seconds. It's also possible to backtrack through submenus.
Replace VFL\UI\PopupMenu.lua
Code:
-- PopupMenu.lua
-- VFL - Venificus' Function Library
-- (C)2005-2006 Bill Johnson (Venificus of Eredar server)
--
-- Implementation of a generalized popup menu object.
-- Amount of time to wait for the mouse to not be over a menu before autoclosing
-- it
local mouseOutDelay = 2;
-- An empty popup menu
local emptyMenu = {
{ text = "|c00888888(Empty menu)|r" }
};
-----------------------------------------------------------------------------
-- @class VFLUI.PopMenu
--
-- An entity for displaying a hierarchical menu whose display entities are
-- VFL Selectables.
--
-- PopupMenu:Begin(targetFrame, targetPoint, offx, offy) functions like SetPoint
-- and sets where the popup menu will originate from.
--
-- PopupMenu:Expand(anchorFrame, data) expands the tree, hanging it off the given
-- frame.
-----------------------------------------------------------------------------
-- Rendering functions
local function UnivMenuApplyData(cell, data)
-- Set pictorials
if(data.isSubmenu) then
cell.icon:Show();
elseif(data.texture) then
cell.icon:SetTexture(data.texture);
cell.icon:Show();
else
cell.icon:Hide();
end
-- Set text color
if(data.color) then
cell.text:SetTextColor(data.color.r, data.color.g, data.color.b);
else
cell.text:SetTextColor(1,1,1);
end
-- Set text
cell:Enable();
cell.text:SetText(data.text);
cell:SetScript("OnClick", data.OnClick);
cell:SetScript("OnMouseDown", data.OnMouseDown);
cell:SetScript("OnMouseUp", data.OnMouseUp);
-- Show highlight
if(data.hlt) then cell:Select(); else cell:Unselect(); end
end
local function LeftMenuApplyData(cell, data)
cell:SetPurpose(3);
cell.icon:SetTexture("Interface\\Addons\\VFL\\Skin\\sb_left");
UnivMenuApplyData(cell, data);
end
local function RightMenuApplyData(cell, data)
cell:SetPurpose(2);
cell.icon:SetTexture("Interface\\Addons\\VFL\\Skin\\sb_right");
UnivMenuApplyData(cell, data);
end
local function DisableListContents(list)
list:SetAlpha(0.45);
for x in list:_GetGrid():Iterator() do
x:Disable();
end
end
-- Popmenu object
VFLUI.PopMenu = {};
VFLUI.PopMenu.__index = VFLUI.PopMenu;
function VFLUI.PopMenu:new()
local self = {};
setmetatable(self, VFLUI.PopMenu);
-- Create unique escape handler
self.esch = function() self:Release(); end
return self;
end
-- Determine if the popup tree is currently in use.
function VFLUI.PopMenu:IsInUse()
if self.menus then return true; else return nil; end
end
-- Determine if the mouse is over this menu
function VFLUI.PopMenu:MouseIsOver()
for k,v in pairs(self.menus) do if MouseIsOver(v) then return true; end end
return false;
end
-- Schedule a repeating check to see if the mouse is still over the menu or not.
function VFLUI.PopMenu:MouseOutCheck()
self.mouseout_schedule = nil;
if not self.menus then return; end
local mouseout = true;
if not self:MouseIsOver() then
self.mouseout_schedule = VFL.schedule(mouseOutDelay, VFLUI.PopMenu.MouseOutFinal, self);
else
self.mouseout_schedule = VFL.schedule(0.5, VFLUI.PopMenu.MouseOutCheck, self);
end
end
-- Final mouseout check -- if the mouse is out this time, destroy the menu
function VFLUI.PopMenu:MouseOutFinal()
self.mouseout_schedule = nil;
if not self:MouseIsOver() then
self:Release();
else
-- Back to regular checks.
self.mouseout_schedule = VFL.schedule(1, VFLUI.PopMenu.MouseOutCheck, self);
end
end
-- Start a new popup tree. Destroys any old popup tree and sets the anchor point for the
-- newly created tree
function VFLUI.PopMenu:Begin(cellWidth, cellHeight, frame, point, dx, dy)
-- Sanify parameters
if (not frame) then return false; end
if (not dx) then dx = 0; dy = 0; end
-- Destroy preexisting menus
if(self.menus) then self:Release(); end
-- Compute orientation
-- Get center
local UICx, UICy = UIParent:GetCenter();
-- Translate 1/4 screenwidth rightward
UICx = UICx + ((UIParent:GetRight() - UICx)/2);
-- Go universal
UICx, UICy = GetUniversalCoords(UIParent, UICx, UICy);
-- Get universal coords of new frame center
local MCx, MCy = GetPoint(frame, point);
MCx, MCy = GetUniversalCoords(frame, MCx + dx, MCy + dy);
if(MCx > UICx) then self.orientation = 1; else self.orientation = 2; end
-- Move the menu slightly so the mouse is within the menu boundaries.
if self.orientation == 1 then
dx = dx + 10; dy = dy + 10;
else
dx = dx - 10; dy = dy + 10;
end
-- Initialize state
self.af = frame; self.ap = point; self.adx = dx; self.ady = dy; self.cdx = cellWidth; self.cdy = cellHeight;
self.menus = {};
-- If no point was provided, treat as a dropdown
if not point then
if self.orientation == 1 then
self.ap = "BOTTOMRIGHT";
else
self.ap = "BOTTOMLEFT";
end
end
end
-- Expand the popup tree by attaching a menu with the given data, anchored to
-- the given frame.
function VFLUI.PopMenu:Expand(aFrame, data, limit)
-- Sanity check on state
if not self.menus then error("VFLUI.PopMenu: attempt to expand uninitialized menu"); end
-- Sanity check on data
if (not data) or (table.getn(data) == 0) then data = emptyMenu; end
-- Determine menu depth
local menu_level = nil;
if aFrame then
menu_level = aFrame._menu_level + 1;
else
menu_level = 1;
end
if not menu_level then error("could not determine menu level"); end
-- Close all menus deeper than the depth of the new menu
if menu_level <= #(self.menus) then
for i = 1, #(self.menus) - menu_level + 1 do
self.menus[i].decor:Destroy(); self.menus[i].decor = nil;
self.menus[i]:Destroy();
end
for i=1, #(self.menus) - menu_level + 1 do table.remove(self.menus, 1); end
end
-- Determine layout parameters
local aHoldPoint, aGrabPoint, fnad, dy, dx = nil, nil, nil, 0, 5;
if(self.orientation == 1) then -- left-oriented
aHoldPoint = "TOPLEFT"; aGrabPoint = "TOPRIGHT"; fnad = LeftMenuApplyData;
dx = -dx;
else -- right oriented
aHoldPoint = "TOPRIGHT"; aGrabPoint = "TOPLEFT"; fnad = RightMenuApplyData;
end
-- If we've not yet created a menu...
if(table.getn(self.menus) == 0) then
VFL.AddEscapeHandler(self.esch);
aFrame = self.af; aHoldPoint = self.ap; dx = dx + self.adx; dy = self.ady;
else
dy = 0; -- Shift things upward by units of one cell (?)
end
-- Create the decor frame to "look pretty"
local decor = VFLUI.AcquireFrame("Frame");
decor:SetParent("UIParent");
decor:SetFrameStrata("FULLSCREEN_DIALOG");
decor:SetFrameLevel(10);
decor:SetBackdrop(VFLUI.BlackDialogBackdrop);
-- Create the menu
local menuSz = table.getn(data);
if limit then menuSz = math.min(limit, menuSz); end
local menu = VFLUI.List:new(decor, self.cdy, function()
local c = VFLUI.Selectable:new(nil, Fonts.Default10);
c._menu_level = menu_level;
c.Destroy = VFL.hook(c.Destroy, function(x)
x._menu_level = nil;
end);
c.OnDeparent = c.Destroy;
return c;
end);
menu:SetWidth(self.cdx); menu:SetHeight((menuSz * self.cdy) + 1);
menu:SetDataSource(fnad, VFL.ArrayLiterator(data));
-- Assign and anchor the decor to the menu
menu.decor = decor;
decor:SetPoint("TOPLEFT", menu, "TOPLEFT", -5, 4);
decor:SetPoint("BOTTOMRIGHT", menu, "BOTTOMRIGHT", 5, -5);
-- Anchor the menu to the appropriate point
menu:SetPoint(aGrabPoint, aFrame, aHoldPoint, TransformCoords(aFrame, UIParent, dx, dy));
-- Insert our new menu into the hierarchy
table.insert(self.menus, 1, menu);
-- Show the new menu
menu:Rebuild();
menu:Show(.2); decor:Show(.2);
-- Check for off-screenage
local bx, by = 0, 0;
if not menu:GetLeft() then return; end
if(menu:GetLeft() < 0) then
bx = -menu:GetLeft();
else
local univMenuRight, univScreenRight = ToUniversalAxis(menu, menu:GetRight()), ToUniversalAxis(UIParent, UIParent:GetRight());
if( univMenuRight > univScreenRight ) then
bx = -ToLocalAxis(menu, univMenuRight - univScreenRight);
end
end
if(menu:GetBottom() < 0) then by = -menu:GetBottom(); end
self:Bump(bx, by);
-- Schedule mouseout checks
if not self.mouseout_schedule then self:MouseOutCheck(); end
end
-- Bump the popup tree by moving the first anchor by the given distance.
function VFLUI.PopMenu:Bump(dx, dy)
if(dx == 0) and (dy == 0) then return; end
self.adx = self.adx + dx; self.ady = self.ady + dy;
local firstm = self.menus[table.getn(self.menus)];
if not firstm then return; end
local aGrabPoint = "TOPLEFT";
if(self.orientation == 1) then aGrabPoint = "TOPRIGHT"; end
firstm:SetPoint(aGrabPoint, self.af, self.ap, self.adx, self.ady);
end
-- Release and destroy the entire popup tree.
function VFLUI.PopMenu:Release()
if not self.menus then return; end
local m = table.remove(self.menus);
while m do
m:Destroy();
m.decor:Destroy(); m.decor = nil;
m = table.remove(self.menus);
end
self.menus = nil;
VFL.RemoveEscapeHandler(self.esch);
if self.mouseout_schedule then
VFL.unschedule(self.mouseout_schedule);
self.mouseout_schedule = nil;
end
end
--- Global popup menu object.
VFL.poptree = VFLUI.PopMenu:new();