/* Number of times to flash when menu item selected */
const MENU_ITEM_FLASH_COUNT = 3;
/* Delay between flashes */
const MENU_ITEM_FLASH_DELAY = 50;
/* Grace period before a menu is considered sticky */
const MENU_STICKY_GRACE_PERIOD = 350;
/* Offset of submenu relative to parent title */
const SUBMENU_LEFT_OFFSET = 26;
function initMenus() {
/* Used to keep track of a menu to be hidden on mouseup */
let m_openMenus = [];
let m_menuMousedown = false;
let m_menuDragging = false;
let m_menuDraggingTimer = null;
let m_menuItemFlashing = false;
/* Initialize menubar */
$(".menubar .clock").on("click", function () {
g_menubarShowDate = !g_menubarShowDate;
updateClock();
});
/* Hide all the menu items */
$(".menu .menu-items").each(function () {
let $this = $(this);
$this.hide();
});
/* Build icon menus (like the Apple menu) */
$(".menubar > .menu .icon").each(function () {
let $this = $(this);
let icon = $this.attr("data-icon");
if (icon) {
let iconPath = SETTINGS_ICONS_SMALL_PATH + icon + ".png";
$this.css("background-image", "url(" + iconPath + ")");
}
});
/* Build shortcuts and check marks */
$(".menu .menu-items > div").not(".submenu").each(function () {
let $this = $(this);
let shortcut = $this.attr("data-shortcut");
let checked = $this.attr("data-checked");
if (shortcut) {
$this.append("
");
}
if (checked) {
$this.prepend("
");
}
});
$(".menubar").on("mousedown", function (event) {
/* Prevents a window from losing focus and
displaying its blur state */
event.preventDefault();
});
$(".menu-items .submenu .title").each(function () {
let $this = $(this);
/* Add an arrow icon */
$this.append("⯈
");
});
/* Close any submenus */
$(".menu > .menu-items > div").on("mouseenter", function () {
let $this = $(this);
m_openMenus.forEach(function (menuId, index) {
/* Make sure it's a submenu */
if (menuId !== $this.attr("id") && $("#" + menuId).hasClass("submenu")) {
$("#" + menuId + " .menu-items").hide();
/* Remove the submenu from the array */
m_openMenus.splice(index, 1);
}
});
});
let $menuItemsDiv = $(".menu-items > div");
/* Highlight menu item */
$menuItemsDiv.not(".separator").on("mouseenter", function () {
let $this = $(this);
/* Conditional menu items can toggle the "disabled" class
so this check will handle them instead the function selector */
if (m_menuItemFlashing || $this.is(".disabled"))
return;
$this.addClass("active");
});
/* Remove highlight from menu item */
$menuItemsDiv.not(".separator").on("mouseleave", function () {
let $this = $(this);
$this.removeClass("active");
}).on("mouseup", function (event) {
/* The menu has been clicked */
let $this = $(this);
let flashCount = MENU_ITEM_FLASH_COUNT;
let action = $this.attr("data-action");
/* Prevent other mouseup actions */
event.stopPropagation();
/* Conditional menu items can toggle the "disabled" class
so this check will handle them instead the function selector.
The "none" action will prevent submenu titles from flashing. */
if (m_menuItemFlashing || $this.is(".disabled") || action === "none") {
/* Close all menus */
$(".desktop").trigger("mouseup");
return;
}
m_menuItemFlashing = true;
/* Flash effect */
while (flashCount--) {
$this.queue(function () {
$this.removeClass("active").dequeue();
}).delay(MENU_ITEM_FLASH_DELAY).queue(function () {
$this.addClass("active").dequeue();
}).delay(MENU_ITEM_FLASH_DELAY);
}
/* Action handler */
$this.queue(function () {
if (action) {
Actions.parseFunction(action);
}
$this.dequeue();
}).queue(function () {
/* Close all menus */
$(".desktop").trigger("mouseup");
$this.removeClass("active");
m_menuItemFlashing = false;
$this.dequeue();
});
});
let $menuTitle = $(".menu > .title");
/* Sticky menus will hide one menu and show another if a menu has been
clicked and the button released */
$menuTitle.on("mouseenter", function () {
let $this = $(this);
/* Make sure we don't close the active menu when you enter its title */
if (!m_openMenus.includes($this.parents(".menu").attr("id"))) {
m_openMenus.forEach(function (menuId) {
$("#" + menuId + " .title").removeClass("active");
$("#" + menuId + " .menu-items").hide();
});
m_openMenus = [];
}
if (m_menuMousedown) {
$this.trigger("mousedown");
}
});
/* Show the menu items */
$menuTitle.on("mousedown", function () {
let $this = $(this);
let $menuItems = $this.siblings(".menu-items");
let menuId = $this.parents(".menu").attr("id");
if (m_menuMousedown && m_openMenus.includes(menuId)) {
/* Clicking the title of an active menu will close it */
$(".desktop").trigger("mouseup");
} else {
$this.addClass("active");
/* Toggle conditional menu items */
toggleConditionalMenuItems($menuItems);
$menuItems.show();
m_openMenus = [menuId];
m_menuMousedown = true;
}
/* Dragging menus will close them when mouseup on a title, but
only after a timer (or else it's a sticky menu) */
m_menuDraggingTimer = setInterval(function () {
m_menuDragging = true;
}, MENU_STICKY_GRACE_PERIOD);
});
/* Show the submenu items */
$(".submenu").on("mouseenter", function () {
let $this = $(this);
/* Conditional menu items can toggle the "disabled" class
so this check will handle them instead the function selector */
if (m_menuItemFlashing || $this.is(".disabled"))
return;
if (m_menuMousedown) {
let $menuItems = $this.children(".menu-items");
$this.addClass("active");
/* Toggle conditional menu items */
toggleConditionalMenuItems($menuItems);
$menuItems.show();
/* Position the submenu properly relative to its title */
$menuItems.css("left", $this.width() + SUBMENU_LEFT_OFFSET);
m_openMenus.push($this.attr("id"));
}
});
/* Moving into the spacer will close all menus, but keep sticky menus active.
This is done instead of $(".menu .title").leave() because otherwise the
active menu will close once you leave its title. */
$(".menubar > div:not(.menu)").on("mouseenter", function () {
m_openMenus.forEach(function (menuId) {
$("#" + menuId + " .title").removeClass("active");
$("#" + menuId + " .menu-items").hide();
});
m_openMenus = [];
});
/* Close all menus if we mouseup over non-enabled menu items */
$(".desktop, .menubar > div:not(.menu), .menu-items > div.disabled, .menu-items > div.separator").on("mouseup", function () {
let $this = $(this);
/* Conditional menu items can toggle the "disabled" class
so this check will handle them instead the function selector */
if ($this.is(".menu-items > div:not(.disabled)") && $this.is(".menu-items > div:not(.separator)"))
return;
/* Clear the menu dragging timer */
clearInterval(m_menuDraggingTimer);
/* Stop dragging a menu if we are */
if (m_menuDragging)
m_menuDragging = false;
m_openMenus.forEach(function (menuId) {
$("#" + menuId + " .title").removeClass("active");
$("#" + menuId + " .menu-items").hide();
});
m_openMenus = [];
m_menuMousedown = false;
});
}
/* TODO: Get this fast and working */
/*function refreshWindowMenu() {
let $windowMenu = $("#menuWindow .menu-items");
let $wrapper = Object;
let title = "";
$windowMenu.append("Desktop
");
if (g_openWindows.length) {
$windowMenu.append("");
}
const infoWindows = g_openWindows.filter(e => e.type === "info");
infoWindows.sort();
infoWindows.forEach(function(infoWindow){
$wrapper = $(".desktop .window-wrapper[data-id=\"" + infoWindow.id + "\"][data-type=\"" + infoWindow.type + "\"]");
title = $wrapper.find(".window .header .title").text();
console.log(title);
});
}*/
function toggleConditionalMenuItems($menuItems) {
$menuItems.children("div").each(function () {
let $this = $(this);
let requires = $this.attr("data-requires");
if (requires) {
let fn = MenuItemRequires[requires];
if (fn()) {
$this.removeClass("disabled");
} else {
$this.addClass("disabled");
}
}
});
}
const MenuItemRequires = {
"focusedFigure": function () {
let $figure = $("figure");
return $figure.hasClass("focus");
},
"focusedWindow": function () {
let $window = $(".window");
return $window.hasClass("focus");
}
};