$MenuBar.DIVIDER = "DIVIDER"; function $MenuBar(menus){ var $ = jQuery; var $G = $(self); var $menus = $(E("div")).addClass("menus"); $menus.attr("touch-action", "none"); var selecting_menus = false; var _html = function(menus_key){ return menus_key.replace(/&(.)/, function(m){ return "" + m[1] + ""; }); }; var _hotkey = function(menus_key){ return menus_key[menus_key.indexOf("&")+1].toUpperCase(); }; var close_menus = function(){ $menus.find(".menu-button").trigger("release"); // Close any rogue floating submenus $(".menu-popup").hide(); }; var is_disabled = function(item){ if(typeof item.enabled === "function"){ return !item.enabled(); }else if(typeof item.enabled === "boolean"){ return !item.enabled; }else{ return false; } }; // TODO: API for context menus (i.e. floating menu popups) function $MenuPopup(menu_items){ var $menu_popup = $(E("div")).addClass("menu-popup"); var $menu_popup_table = $(E("table")).addClass("menu-popup-table").appendTo($menu_popup); $.map(menu_items, function(item){ var $row = $(E("tr")).addClass("menu-row").appendTo($menu_popup_table) if(item === $MenuBar.DIVIDER){ var $td = $(E("td")).attr({colspan: 4}).appendTo($row); var $hr = $(E("hr")).addClass("menu-hr").appendTo($td); }else{ var $item = $row.addClass("menu-item"); var $checkbox_area = $(E("td")).addClass("menu-item-checkbox-area"); var $label = $(E("td")).addClass("menu-item-label"); var $shortcut = $(E("td")).addClass("menu-item-shortcut"); var $submenu_area = $(E("td")).addClass("menu-item-submenu-area"); $item.append($checkbox_area, $label, $shortcut, $submenu_area); $item.attr("tabIndex", -1); $label.html(_html(item.item)); $shortcut.text(item.shortcut); $menu_popup.on("update", function(){ $item.attr("disabled", is_disabled(item)); if(item.checkbox && item.checkbox.check){ $checkbox_area.text(item.checkbox.check() ? "✓" : ""); } }); $item.on("pointerover", function(){ $menu_popup.triggerHandler("update"); $item.focus(); }); if(item.checkbox){ $checkbox_area.text("✓"); } if(item.submenu){ $submenu_area.html(''); var $submenu_popup = $MenuPopup(item.submenu).appendTo("body"); $submenu_popup.hide(); var open_submenu = function(){ $submenu_popup.show(); $submenu_popup.triggerHandler("update"); var rect = $submenu_area[0].getBoundingClientRect(); $submenu_popup.css({ position: "absolute", left: rect.right, top: rect.top, }); }; var open_tid, close_tid; $item.add($submenu_popup).on("pointerover", function(e){ if(open_tid){clearTimeout(open_tid);} if(close_tid){clearTimeout(close_tid);} }); $item.on("pointerover", function(e){ if(open_tid){clearTimeout(open_tid);} if(close_tid){clearTimeout(close_tid);} open_tid = setTimeout(open_submenu, 200); }); $item.add($submenu_popup).on("pointerout", function(){ $menu_popup.closest(".menu-container").find(".menu-button").focus(); if(open_tid){clearTimeout(open_tid);} if(close_tid){clearTimeout(close_tid);} close_tid = setTimeout(function(){ $submenu_popup.hide(); }, 200); }); $item.on("click pointerdown", open_submenu); } var item_action = function(){ if(item.checkbox){ if(item.checkbox.toggle){ item.checkbox.toggle(); } $menu_popup.triggerHandler("update"); }else if(item.action){ close_menus(); item.action(); } }; $item.on("pointerup", function(e){ if(e.pointerType === "mouse" && e.button !== 0){ return; } item_action(); }); $item.on("pointerover", function(){ if(item.submenu){ $menus.triggerHandler("info", ""); }else{ $menus.triggerHandler("info", item.description || ""); } }); $item.on("pointerout", function(){ if($item.is(":visible")){ $menus.triggerHandler("info", ""); $menu_popup.closest(".menu-container").find(".menu-button").focus(); } }); $item.on("keydown", function(e){ if(e.ctrlKey || e.shiftKey || e.altKey){ return; } if(e.keyCode === 13){ // Enter e.preventDefault(); item_action(); } }); $menu_popup.on("keydown", function(e){ if(e.ctrlKey || e.shiftKey || e.altKey){ return; } if(String.fromCharCode(e.keyCode) === _hotkey(item.item)){ e.preventDefault(); $item.click(); } }); } }); return $menu_popup; } var this_click_opened_the_menu = false; $.each(menus, function(menus_key, menu_items){ var $menu_container = $(E("div")).addClass("menu-container").appendTo($menus); var $menu_button = $(E("div")).addClass("menu-button").appendTo($menu_container); var $menu_popup = $MenuPopup(menu_items).appendTo($menu_container); var menu_id = menus_key.replace("&", "").replace(/ /g, "-").toLowerCase(); $menu_button.addClass("" + menu_id + "-menu-button"); if(menu_id == "extras"){ // TODO: refactor shared key string, move to function try{ if(localStorage["jspaint extras menu visible"] != "true"){ $menu_button.hide(); } }catch(e){} } $menu_popup.hide(); $menu_button.html(_html(menus_key)); $menu_button.attr("tabIndex", -1) $menu_container.on("keydown", function(e){ var $focused_item = $menu_popup.find(".menu-item:focus"); switch(e.keyCode){ case 37: // Left $menu_container.prev().find(".menu-button").trigger("pointerdown"); break; case 39: // Right if($focused_item.find(".menu-item-submenu-area:not(:empty)").length){ $focused_item.click(); $(".menu-popup .menu-item").first().focus(); e.preventDefault(); }else{ $menu_container.next().find(".menu-button").trigger("pointerdown"); } break; case 40: // Down if($menu_popup.is(":visible") && $focused_item.length){ var $next = $focused_item.next(); while($next.length && !$next.is(".menu-item")){ $next = $next.next(); } $next.focus(); }else{ $menu_button.trigger("pointerdown"); $menu_popup.find(".menu-item").first().focus(); } break; case 38: // Up if($menu_popup.is(":visible") && $focused_item.length){ var $prev = $focused_item.prev(); while($prev.length && !$prev.is(".menu-item")){ $prev = $prev.prev(); } $prev.focus(); }else{ $menu_button.trigger("pointerdown"); // or maybe do nothing? $menu_popup.find(".menu-item").last().focus(); } break; } }); $G.on("keydown", function(e){ if(e.ctrlKey){ // Ctrl+... if(e.keyCode !== 17){ // anything but Ctrl close_menus(); } return; } if(e.altKey){ if(String.fromCharCode(e.keyCode) === _hotkey(menus_key)){ e.preventDefault(); $menu_button.trigger("pointerdown"); } } }); $menu_button.on("pointerdown pointerover", function(e){ if(e.type === "pointerover" && !selecting_menus){ return; } if(e.type !== "pointerover"){ if(!$menu_button.hasClass("active")){ this_click_opened_the_menu = true; } } close_menus(); $menu_button.focus(); $menu_button.addClass("active"); $menu_popup.show(); $menu_popup.triggerHandler("update"); selecting_menus = true; $menus.triggerHandler("info", ""); }); $menu_button.on("pointerup", function(e){ if(this_click_opened_the_menu){ this_click_opened_the_menu = false; return; } if($menu_button.hasClass("active")){ close_menus(); } }); $menu_button.on("release", function(e){ selecting_menus = false; $menu_button.removeClass("active"); $menu_popup.hide(); $menus.triggerHandler("default-info"); }); }); $G.on("keypress", function(e){ if(e.keyCode === 27){ // Esc close_menus(); } }); $G.on("blur", function(e){ // console.log("blur", e.target, document.activeElement); close_menus(); }); $G.on("pointerdown pointerup", function(e){ if($(e.target).closest(".menus, .menu-popup").length === 0){ // console.log(e.type, "occurred outside of menus (on ", e.target, ") so..."); close_menus(); } }); return $menus; }