local awful = require("awful") local gears = require("gears") local menubar_utils = require("menubar.utils") local wibox = require("wibox") local beautiful = require("beautiful") local awmtk = require("awmtk") local function cascade_close(path,limit) for I = #path,limit or 1,-1 do if path[I] then path[I].visible = false end path[I] = nil end end local function merge_into_widget(widget,args) for k,v in ipairs(args) do table.insert(widget,v) end end local function get_button_container() --This function prevents mouse.current_widget_geometry from picking up textboxes local output = {} local geo = mouse.current_widgets for k,v in pairs(geo) do if tostring(v):match("^widget_background") then output = mouse.current_widget_geometries[k] break end end return output end local function loader(options) local style = awmtk.style(awmtk.defaults,options.style or {},"menu_") options = options or {} -- Define styles local button_bg_on = style.menu_button_bg_focus local button_bg_off = style.menu_button_bg_normal local userlinks = wibox.widget { layout = ( options.vertical and wibox.layout.fixed.vertical ) or wibox.layout.fixed.horizontal, spacing = style.menu_container_spacing or 4, } local button_template = function(v) return { layout = ( options.vertical and wibox.layout.fixed.horizontal ) or wibox.layout.fixed.vertical, (v[3] and { widget = wibox.widget.imagebox, resize = true, image = v[3], }), { markup = v[1] or "", widget = wibox.widget.textbox, ellipsize = "end" }, spacing = 2 } end local button_style = { bg = button_bg_off, forced_height = style.menu_button_height or style.menu_height or 24, forced_width = style.menu_button_width or style.menu_width or 140 } local menu_template = function(tree_layer) return { layout = ( options.vertical and wibox.layout.fixed.vertical ) or wibox.layout.fixed.horizontal, spacing = ( options.vertical and style.menu_container_spacing_vertical ) or style.menu_container_spacing_horizontal, tree_layer = tree_layer, } end local menu_popup_template = { visible = false, ontop = true, preferred_positions = (options.vertical and {"right","left"}) or {"top","bottom"}, preferred_anchors = (options.inverse and {"back","front"}) or {"front","back"}, } --Create menu root local menu = menu_template(1) local tree_path = {"spacer"} --Insert widgets above/before the menu merge_into_widget(menu,options.before or {}) local function generate_menu(leaf,menu) --Iterate over a table of widget defining tables for k,v in ipairs(leaf) do if type(v) ~= "table" then --Error if the structure is invalid error("Invalid leaf type "..type(v).." in menu tree") else local tree_layer = menu.tree_layer+1 --Create a button widget local new_button = style.button(button_template(v),button_style) if type(v[2]) == "table" then --Create a popup template for the new menu leaf local new_popup = menu_template(tree_layer) --Add buttons and widgets to the new leaf merge_into_widget(new_popup,v[2].before or {}) generate_menu(v[2],new_popup) merge_into_widget(new_popup,v[2].after or {}) new_popup = awful.popup(style.container(new_popup,menu_popup_template)) new_button:connect_signal("mouse::enter",function() --Hide cascading leaves of a branch when leaf changes if menu.current_selection and (menu.current_selection ~= new_popup) then cascade_close(tree_path,tree_layer) end --Move the new popup widget closer to mouse and align it precisely to the button if mouse.current_widget_geometry then local geo = get_button_container() tree_path[tree_layer] = new_popup --Apparently it's a thing that needed fixing. --I don't question it, and neither should you. if geo and geo.x and geo.y then new_popup:move_next_to(geo) end --previous method call aligns the popup to the button, --NOT the buttons within the popup. So we do that ourselves (somewhat). if new_popup.current_anchor == "front" then new_popup.y = new_popup.y - style.menu_container_inner_margin else new_popup.y = new_popup.y + style.menu_container_inner_margin end new_popup.visible = true menu.current_selection = new_popup end end) elseif type(v[2]) == "function" then new_button:connect_signal("button::press",function() cascade_close(tree_path,1) v[2]() end) elseif type(v[2]) == "string" then new_button:connect_signal("button::press",function() cascade_close(tree_path,1) awful.spawn(v[2]) end) end new_button:connect_signal("mouse::enter",function() --Set button bg if menu.current_button then menu.current_button.bg = button_bg_off end menu.current_button = new_button menu.current_button.bg = button_bg_on end) table.insert(menu,new_button) end end end generate_menu(options.items,menu) --Insert widgets below/after the menu merge_into_widget(menu,options.after or {}) menu = awful.popup(style.container(menu,menu_popup_template)) tree_path[1] = menu menu.toggle = function(x,y) if (not x) or (not y) then x = mouse.coords().x y = mouse.coords().y end menu.x = x menu.y = y if menu.x+menu.width > menu.screen.geometry.width then menu.x = menu.x-menu.width end if menu.y+menu.height > menu.screen.geometry.height then menu.y = menu.y-menu.height end if menu.visible then cascade_close(tree_path,2) end tree_path[1] = menu menu.visible = (not menu.visible) end menu.show = function(self) menu.toggle() end return menu end return loader