menus.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /* Number of times to flash when menu item selected */
  2. const MENU_ITEM_FLASH_COUNT = 3;
  3. /* Delay between flashes */
  4. const MENU_ITEM_FLASH_DELAY = 50;
  5. /* Grace period before a menu is considered sticky */
  6. const MENU_STICKY_GRACE_PERIOD = 350;
  7. /* Offset of submenu relative to parent title */
  8. const SUBMENU_LEFT_OFFSET = 26;
  9. function initMenus() {
  10. /* Used to keep track of a menu to be hidden on mouseup */
  11. let m_openMenus = [];
  12. let m_menuMousedown = false;
  13. let m_menuDragging = false;
  14. let m_menuDraggingTimer = null;
  15. let m_menuItemFlashing = false;
  16. /* Initialize menubar */
  17. $(".menubar .clock").on("click", function () {
  18. g_menubarShowDate = !g_menubarShowDate;
  19. updateClock();
  20. });
  21. /* Hide all the menu items */
  22. $(".menu .menu-items").each(function () {
  23. let $this = $(this);
  24. $this.hide();
  25. });
  26. /* Build icon menus (like the Apple menu) */
  27. $(".menubar > .menu .icon").each(function () {
  28. let $this = $(this);
  29. let icon = $this.attr("data-icon");
  30. if (icon) {
  31. let iconPath = SETTINGS_ICONS_SMALL_PATH + icon + ".png";
  32. $this.css("background-image", "url(" + iconPath + ")");
  33. }
  34. });
  35. /* Build shortcuts and check marks */
  36. $(".menu .menu-items > div").not(".submenu").each(function () {
  37. let $this = $(this);
  38. let shortcut = $this.attr("data-shortcut");
  39. let checked = $this.attr("data-checked");
  40. if (shortcut) {
  41. $this.append("<div class=\"shortcut\"><div class=\"symbol\">&#8984;</div><div class=\"char\">" + shortcut + "</div></div>");
  42. }
  43. if (checked) {
  44. $this.prepend("<div class=\"checked\">&nbsp;</div>");
  45. }
  46. });
  47. $(".menubar").on("mousedown", function (event) {
  48. /* Prevents a window from losing focus and
  49. displaying its blur state */
  50. event.preventDefault();
  51. });
  52. $(".menu-items .submenu .title").each(function () {
  53. let $this = $(this);
  54. /* Add an arrow icon */
  55. $this.append("<div class=\"arrow\">&#11208;</div>");
  56. });
  57. /* Close any submenus */
  58. $(".menu > .menu-items > div").on("mouseenter", function () {
  59. let $this = $(this);
  60. m_openMenus.forEach(function (menuId, index) {
  61. /* Make sure it's a submenu */
  62. if (menuId !== $this.attr("id") && $("#" + menuId).hasClass("submenu")) {
  63. $("#" + menuId + " .menu-items").hide();
  64. /* Remove the submenu from the array */
  65. m_openMenus.splice(index, 1);
  66. }
  67. });
  68. });
  69. let $menuItemsDiv = $(".menu-items > div");
  70. /* Highlight menu item */
  71. $menuItemsDiv.not(".separator").on("mouseenter", function () {
  72. let $this = $(this);
  73. /* Conditional menu items can toggle the "disabled" class
  74. so this check will handle them instead the function selector */
  75. if (m_menuItemFlashing || $this.is(".disabled"))
  76. return;
  77. $this.addClass("active");
  78. });
  79. /* Remove highlight from menu item */
  80. $menuItemsDiv.not(".separator").on("mouseleave", function () {
  81. let $this = $(this);
  82. $this.removeClass("active");
  83. }).on("mouseup", function (event) {
  84. /* The menu has been clicked */
  85. let $this = $(this);
  86. let flashCount = MENU_ITEM_FLASH_COUNT;
  87. let action = $this.attr("data-action");
  88. /* Prevent other mouseup actions */
  89. event.stopPropagation();
  90. /* Conditional menu items can toggle the "disabled" class
  91. so this check will handle them instead the function selector.
  92. The "none" action will prevent submenu titles from flashing. */
  93. if (m_menuItemFlashing || $this.is(".disabled") || action === "none") {
  94. /* Close all menus */
  95. $(".desktop").trigger("mouseup");
  96. return;
  97. }
  98. m_menuItemFlashing = true;
  99. /* Flash effect */
  100. while (flashCount--) {
  101. $this.queue(function () {
  102. $this.removeClass("active").dequeue();
  103. }).delay(MENU_ITEM_FLASH_DELAY).queue(function () {
  104. $this.addClass("active").dequeue();
  105. }).delay(MENU_ITEM_FLASH_DELAY);
  106. }
  107. /* Action handler */
  108. $this.queue(function () {
  109. if (action) {
  110. Actions.parseFunction(action);
  111. }
  112. $this.dequeue();
  113. }).queue(function () {
  114. /* Close all menus */
  115. $(".desktop").trigger("mouseup");
  116. $this.removeClass("active");
  117. m_menuItemFlashing = false;
  118. $this.dequeue();
  119. });
  120. });
  121. let $menuTitle = $(".menu > .title");
  122. /* Sticky menus will hide one menu and show another if a menu has been
  123. clicked and the button released */
  124. $menuTitle.on("mouseenter", function () {
  125. let $this = $(this);
  126. /* Make sure we don't close the active menu when you enter its title */
  127. if (!m_openMenus.includes($this.parents(".menu").attr("id"))) {
  128. m_openMenus.forEach(function (menuId) {
  129. $("#" + menuId + " .title").removeClass("active");
  130. $("#" + menuId + " .menu-items").hide();
  131. });
  132. m_openMenus = [];
  133. }
  134. if (m_menuMousedown) {
  135. $this.trigger("mousedown");
  136. }
  137. });
  138. /* Show the menu items */
  139. $menuTitle.on("mousedown", function () {
  140. let $this = $(this);
  141. let $menuItems = $this.siblings(".menu-items");
  142. let menuId = $this.parents(".menu").attr("id");
  143. if (m_menuMousedown && m_openMenus.includes(menuId)) {
  144. /* Clicking the title of an active menu will close it */
  145. $(".desktop").trigger("mouseup");
  146. } else {
  147. $this.addClass("active");
  148. /* Toggle conditional menu items */
  149. toggleConditionalMenuItems($menuItems);
  150. $menuItems.show();
  151. m_openMenus = [menuId];
  152. m_menuMousedown = true;
  153. }
  154. /* Dragging menus will close them when mouseup on a title, but
  155. only after a timer (or else it's a sticky menu) */
  156. m_menuDraggingTimer = setInterval(function () {
  157. m_menuDragging = true;
  158. }, MENU_STICKY_GRACE_PERIOD);
  159. });
  160. /* Show the submenu items */
  161. $(".submenu").on("mouseenter", function () {
  162. let $this = $(this);
  163. /* Conditional menu items can toggle the "disabled" class
  164. so this check will handle them instead the function selector */
  165. if (m_menuItemFlashing || $this.is(".disabled"))
  166. return;
  167. if (m_menuMousedown) {
  168. let $menuItems = $this.children(".menu-items");
  169. $this.addClass("active");
  170. /* Toggle conditional menu items */
  171. toggleConditionalMenuItems($menuItems);
  172. $menuItems.show();
  173. /* Position the submenu properly relative to its title */
  174. $menuItems.css("left", $this.width() + SUBMENU_LEFT_OFFSET);
  175. m_openMenus.push($this.attr("id"));
  176. }
  177. });
  178. /* Moving into the spacer will close all menus, but keep sticky menus active.
  179. This is done instead of $(".menu .title").leave() because otherwise the
  180. active menu will close once you leave its title. */
  181. $(".menubar > div:not(.menu)").on("mouseenter", function () {
  182. m_openMenus.forEach(function (menuId) {
  183. $("#" + menuId + " .title").removeClass("active");
  184. $("#" + menuId + " .menu-items").hide();
  185. });
  186. m_openMenus = [];
  187. });
  188. /* Close all menus if we mouseup over non-enabled menu items */
  189. $(".desktop, .menubar > div:not(.menu), .menu-items > div.disabled, .menu-items > div.separator").on("mouseup", function () {
  190. let $this = $(this);
  191. /* Conditional menu items can toggle the "disabled" class
  192. so this check will handle them instead the function selector */
  193. if ($this.is(".menu-items > div:not(.disabled)") && $this.is(".menu-items > div:not(.separator)"))
  194. return;
  195. /* Clear the menu dragging timer */
  196. clearInterval(m_menuDraggingTimer);
  197. /* Stop dragging a menu if we are */
  198. if (m_menuDragging)
  199. m_menuDragging = false;
  200. m_openMenus.forEach(function (menuId) {
  201. $("#" + menuId + " .title").removeClass("active");
  202. $("#" + menuId + " .menu-items").hide();
  203. });
  204. m_openMenus = [];
  205. m_menuMousedown = false;
  206. });
  207. }
  208. /* TODO: Get this fast and working */
  209. /*function refreshWindowMenu() {
  210. let $windowMenu = $("#menuWindow .menu-items");
  211. let $wrapper = Object;
  212. let title = "";
  213. $windowMenu.append("<div>Desktop</div>");
  214. if (g_openWindows.length) {
  215. $windowMenu.append("<div class=\"separator\"></div>");
  216. }
  217. const infoWindows = g_openWindows.filter(e => e.type === "info");
  218. infoWindows.sort();
  219. infoWindows.forEach(function(infoWindow){
  220. $wrapper = $(".desktop .window-wrapper[data-id=\"" + infoWindow.id + "\"][data-type=\"" + infoWindow.type + "\"]");
  221. title = $wrapper.find(".window .header .title").text();
  222. console.log(title);
  223. });
  224. }*/
  225. function toggleConditionalMenuItems($menuItems) {
  226. $menuItems.children("div").each(function () {
  227. let $this = $(this);
  228. let requires = $this.attr("data-requires");
  229. if (requires) {
  230. let fn = MenuItemRequires[requires];
  231. if (fn()) {
  232. $this.removeClass("disabled");
  233. } else {
  234. $this.addClass("disabled");
  235. }
  236. }
  237. });
  238. }
  239. const MenuItemRequires = {
  240. "focusedFigure": function () {
  241. let $figure = $("figure");
  242. return $figure.hasClass("focus");
  243. },
  244. "focusedWindow": function () {
  245. let $window = $(".window");
  246. return $window.hasClass("focus");
  247. }
  248. };