jquery.cxtmenu.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /* jquery.cxtmenu.js */
  2. /**
  3. * This file is part of cytoscape.js 2.0.2.
  4. *
  5. * Cytoscape.js is free software: you can redistribute it and/or modify it
  6. * under the terms of the GNU Lesser General Public License as published by the Free
  7. * Software Foundation, either version 3 of the License, or (at your option) any
  8. * later version.
  9. *
  10. * Cytoscape.js is distributed in the hope that it will be useful, but WITHOUT
  11. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License along with
  16. * cytoscape.js. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ;(function($){
  19. var defaults = {
  20. menuRadius: 100,
  21. cytoscape: true,
  22. selector: undefined,
  23. commands: [],
  24. fillColor: 'rgba(0, 0, 0, 0.75)',
  25. activeFillColor: 'rgba(92, 194, 237, 0.75)',
  26. activePadding: 20,
  27. indicatorSize: 30,
  28. indicatorColor: 'black',
  29. separatorWidth: 3,
  30. spotlightPadding: 4,
  31. itemColor: 'white',
  32. itemTextShadowColor: 'black'
  33. };
  34. $.fn.cxtmenu = function(params){
  35. var options = $.extend(true, {}, defaults, params);
  36. var fn = params;
  37. var $container = $(this);
  38. var cy;
  39. $container.cytoscape(function(e){
  40. cy = this;
  41. });
  42. var functions = {
  43. destroy: function(){
  44. },
  45. init: function(){
  46. var $parent = $('<div class="cxtmenu"></div>');
  47. var $canvas = $('<canvas></canvas>');
  48. var c2d = $canvas[0].getContext('2d');
  49. var r = options.menuRadius;
  50. var offset = $container.offset();
  51. var containerSize = (r + options.activePadding)*2;
  52. var activeCommandI = undefined;
  53. $container.append( $parent );
  54. $parent.append( $canvas );
  55. $parent.css({
  56. width: containerSize + 'px',
  57. height: containerSize + 'px',
  58. position: 'fixed',
  59. zIndex: 999999,
  60. marginLeft: offset.left - options.activePadding + 'px',
  61. marginTop: offset.top - options.activePadding + 'px'
  62. }).hide();
  63. $canvas[0].width = containerSize;
  64. $canvas[0].height = containerSize;
  65. var commands = options.commands;
  66. var dtheta = 2*Math.PI/(commands.length);
  67. var theta1 = commands.length % 2 !== 0 ? Math.PI/2 : 0;
  68. var theta2 = theta1 + dtheta;
  69. var $items = [];
  70. for( var i = 0; i < commands.length; i++ ){
  71. var command = commands[i];
  72. var midtheta = (theta1 + theta2)/2;
  73. var rx1 = 0.66 * r * Math.cos( midtheta );
  74. var ry1 = 0.66 * r * Math.sin( midtheta );
  75. // console.log(rx1, ry1, theta1, theta2)
  76. var $item = $('<div class="cxtmenu-item"></div>');
  77. $item.css({
  78. color: options.itemColor,
  79. cursor: 'default',
  80. display: 'table',
  81. 'text-align': 'center',
  82. //background: 'red',
  83. position: 'absolute',
  84. 'text-shadow': '-1px -1px ' + options.itemTextShadowColor + ', 1px -1px ' + options.itemTextShadowColor + ', -1px 1px ' + options.itemTextShadowColor + ', 1px 1px ' + options.itemTextShadowColor,
  85. left: '50%',
  86. top: '50%',
  87. 'min-height': r * 0.66,
  88. width: r * 0.66,
  89. height: r * 0.66,
  90. marginLeft: rx1 - r * 0.33,
  91. marginTop: -ry1 -r * 0.33
  92. });
  93. var $content = $('<div class="cxtmenu-content">' + command.content + '</div>');
  94. $content.css({
  95. 'width': r * 0.66,
  96. 'height': r * 0.66,
  97. 'vertical-align': 'middle',
  98. 'display': 'table-cell'
  99. });
  100. $parent.append( $item );
  101. $item.append( $content );
  102. theta1 += dtheta;
  103. theta2 += dtheta;
  104. }
  105. function drawBg( rspotlight ){
  106. rspotlight = rspotlight !== undefined ? rspotlight : rs;
  107. c2d.globalCompositeOperation = 'source-over';
  108. c2d.clearRect(0, 0, containerSize, containerSize);
  109. c2d.fillStyle = options.fillColor;
  110. c2d.beginPath();
  111. c2d.arc(r + options.activePadding, r + options.activePadding, r, 0, Math.PI*2, true);
  112. c2d.closePath();
  113. c2d.fill();
  114. c2d.globalCompositeOperation = 'destination-out';
  115. c2d.strokeStyle = 'white';
  116. c2d.lineWidth = options.separatorWidth;
  117. var commands = options.commands;
  118. var dtheta = 2*Math.PI/(commands.length);
  119. var theta1 = commands.length % 2 !== 0 ? Math.PI/2 : 0;
  120. var theta2 = theta1 + dtheta;
  121. for( var i = 0; i < commands.length; i++ ){
  122. var command = commands[i];
  123. var rx1 = r * Math.cos(theta1);
  124. var ry1 = r * Math.sin(theta1);
  125. c2d.beginPath();
  126. c2d.moveTo(r + options.activePadding, r + options.activePadding);
  127. c2d.lineTo(r + options.activePadding + rx1, r + options.activePadding - ry1);
  128. c2d.closePath();
  129. c2d.stroke();
  130. // var rx2 = r * Math.cos(theta2);
  131. // var ry2 = r * Math.sin(theta2);
  132. // c2d.moveTo(r, r);
  133. // c2d.lineTo(r + rx2, r + ry2);
  134. // c2d.stroke();
  135. theta1 += dtheta;
  136. theta2 += dtheta;
  137. }
  138. c2d.fillStyle = 'white';
  139. c2d.globalCompositeOperation = 'destination-out';
  140. c2d.beginPath();
  141. c2d.arc(r + options.activePadding, r + options.activePadding, rspotlight + options.spotlightPadding, 0, Math.PI*2, true);
  142. c2d.closePath();
  143. c2d.fill();
  144. c2d.globalCompositeOperation = 'source-over';
  145. }
  146. var lastCallTime = 0;
  147. var minCallDelta = 1000/30;
  148. var endCallTimeout;
  149. var firstCall = true;
  150. function rateLimitedCall( fn ){
  151. var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  152. var now = +new Date;
  153. clearTimeout( endCallTimeout );
  154. if( firstCall || now >= lastCallTime + minCallDelta ){
  155. requestAnimationFrame(fn);
  156. lastCallTime = now;
  157. firstCall = false;
  158. } else {
  159. endCallTimeout = setTimeout(function(){
  160. requestAnimationFrame(fn);
  161. lastCallTime = now;
  162. }, minCallDelta * 2);
  163. }
  164. }
  165. var ctrx, ctry, rs;
  166. var tapendHandler;
  167. cy
  168. .on('cxttapstart', options.selector, function(e){
  169. var ele = this;
  170. var rp = ele.renderedPosition();
  171. var rw = ele.renderedWidth();
  172. var rh = ele.renderedHeight();
  173. var scrollLeft = $(window).scrollLeft();
  174. var scrollTop = $(window).scrollTop();
  175. ctrx = rp.x;
  176. ctry = rp.y;
  177. $parent.show().css({
  178. 'left': rp.x - r - scrollLeft,
  179. 'top': rp.y - r - scrollTop
  180. });
  181. rs = Math.max(rw, rh);
  182. rs = 32;
  183. drawBg();
  184. activeCommandI = undefined;
  185. })
  186. .on('cxtdrag', options.selector, function(e){ rateLimitedCall(function(){
  187. var dx = e.originalEvent.pageX - $container.offset().left - ctrx;
  188. var dy = e.originalEvent.pageY - $container.offset().top - ctry;
  189. if( dx === 0 ){ dx = 0.01; }
  190. var d = Math.sqrt( dx*dx + dy*dy );
  191. var cosTheta = (dy*dy - d*d - dx*dx)/(-2 * d * dx);
  192. var theta = Math.acos( cosTheta );
  193. activeCommandI = undefined;
  194. if( d < rs + options.spotlightPadding ){
  195. drawBg();
  196. return;
  197. }
  198. drawBg();
  199. var rx = dx*r / d;
  200. var ry = dy*r / d;
  201. if( dy > 0 ){
  202. theta = Math.PI + Math.abs(theta - Math.PI);
  203. }
  204. var commands = options.commands;
  205. var dtheta = 2*Math.PI/(commands.length);
  206. var theta1 = commands.length % 2 !== 0 ? Math.PI/2 : 0;
  207. var theta2 = theta1 + dtheta;
  208. for( var i = 0; i < commands.length; i++ ){
  209. var command = commands[i];
  210. // console.log(i, theta1, theta, theta2);
  211. var inThisCommand = theta1 <= theta && theta <= theta2
  212. || theta1 <= theta + 2*Math.PI && theta + 2*Math.PI <= theta2;
  213. if( inThisCommand ){
  214. // console.log('in command ' + i)
  215. c2d.fillStyle = options.activeFillColor;
  216. c2d.strokeStyle = 'black';
  217. c2d.lineWidth = 1;
  218. c2d.beginPath();
  219. c2d.moveTo(r + options.activePadding, r + options.activePadding);
  220. c2d.arc(r + options.activePadding, r + options.activePadding, r + options.activePadding, 2*Math.PI - theta1, 2*Math.PI - theta2, true);
  221. c2d.closePath();
  222. c2d.fill();
  223. //c2d.stroke();
  224. activeCommandI = i;
  225. break;
  226. }
  227. theta1 += dtheta;
  228. theta2 += dtheta;
  229. }
  230. c2d.fillStyle = 'white';
  231. c2d.globalCompositeOperation = 'destination-out';
  232. // clear the indicator
  233. c2d.beginPath();
  234. //c2d.arc(r + rx/r*(rs + options.spotlightPadding), r + ry/r*(rs + options.spotlightPadding), options.indicatorSize, 0, 2*Math.PI, true);
  235. c2d.translate( r + options.activePadding + rx/r*(rs + options.spotlightPadding - options.indicatorSize/4), r + options.activePadding + ry/r*(rs + options.spotlightPadding - options.indicatorSize/4) );
  236. c2d.rotate( Math.PI/4 - theta );
  237. c2d.fillRect(-options.indicatorSize/2, -options.indicatorSize/2, options.indicatorSize, options.indicatorSize);
  238. c2d.closePath();
  239. c2d.fill();
  240. c2d.setTransform(1, 0, 0, 1, 0, 0);
  241. // clear the spotlight
  242. c2d.beginPath();
  243. c2d.arc(r + options.activePadding, r + options.activePadding, rs + options.spotlightPadding, 0, Math.PI*2, true);
  244. c2d.closePath();
  245. c2d.fill();
  246. c2d.globalCompositeOperation = 'source-over';
  247. }) })
  248. .on('cxttapend', options.selector, function(e){
  249. var ele = this;
  250. $parent.hide();
  251. if( activeCommandI !== undefined ){
  252. var select = options.commands[ activeCommandI ].select;
  253. if( select ){
  254. select.apply( ele );
  255. }
  256. }
  257. })
  258. .on('cxttapend', function(e){
  259. $parent.hide();
  260. })
  261. ;
  262. }
  263. };
  264. if( functions[fn] ){
  265. return functions[fn].apply(this, Array.prototype.slice.call( arguments, 1 ));
  266. } else if( typeof fn == 'object' || !fn ) {
  267. return functions.init.apply( this, arguments );
  268. } else {
  269. $.error("No such function `"+ fn +"` for jquery.cxtmenu");
  270. }
  271. return $(this);
  272. };
  273. })(jQuery);