main.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. * @Author: CGT (caogtaa@gmail.com)
  3. * @Date: 2020-01-16 22:09:08
  4. * @Last Modified by: CGT (caogtaa@gmail.com)
  5. * @Last Modified time: 2020-01-16 23:46:33
  6. */
  7. 'use strict';
  8. let _menuTemplateCache = null;
  9. let _paramCache = {};
  10. function createNode(uuid) {
  11. let param = { uuid: uuid };
  12. Object.assign(param, _paramCache);
  13. Editor.Scene.callSceneScript('cc-ext-scene-menu', 'create-node', param, (err) => {
  14. if (err)
  15. Editor.log(err);
  16. });
  17. }
  18. function sendCommand(command, param) {
  19. let p = {};
  20. Object.assign(p, _paramCache);
  21. p.customParam = param;
  22. Editor.Ipc.sendToMain(command, p, null);
  23. }
  24. function generateMenuTemplate(conf) {
  25. let result = [];
  26. for (let c of conf) {
  27. // https://electronjs.org/docs/api/menu
  28. let item = {};
  29. item.label = c.name;
  30. // item.click = createNode.bind(c.uuid);
  31. // the menu item auto unbound my function, why?
  32. // so I put uuid in closure
  33. if (c.type == 0) {
  34. // prefab
  35. let uuid = c.uuid;
  36. item.click = () => {
  37. createNode(uuid);
  38. };
  39. } else if (c.type == 1) {
  40. // command
  41. item.click = () => {
  42. sendCommand(c.command, c.param);
  43. };
  44. } else if (c.type == 3) {
  45. let uuid = c.uuid;
  46. item.click = () => {
  47. // createRedDotNode(uuid);
  48. };
  49. } else if (c.submenu) {
  50. item.submenu = generateMenuTemplate(c.submenu);
  51. } else {
  52. // unexpected
  53. }
  54. result.push(item);
  55. }
  56. return result;
  57. }
  58. function loadMenu() {
  59. const fs = require('fs');
  60. let configPath = Editor.Project.path + '/scene-menu-config.json';
  61. if (!fs.existsSync(configPath)) {
  62. // read default config
  63. configPath = Editor.url('packages://cc-ext-scene-menu/default-config.json');
  64. }
  65. fs.readFile(configPath, function (err, data) {
  66. if (err) {
  67. // file not exists
  68. return;
  69. }
  70. try {
  71. // Editor.log(`main.js read data: ${data}`);
  72. let config = JSON.parse(data);
  73. _menuTemplateCache = generateMenuTemplate(config);
  74. } catch (err) {
  75. // if any error occur, old template cache is not replaced
  76. } finally {
  77. }
  78. });
  79. }
  80. function injectContextMenu(webContents) {
  81. if (webContents.__gt_injected) {
  82. // already injected
  83. return;
  84. }
  85. if (webContents != Editor.Window.main.nativeWin.webContents) {
  86. // not cc main app window
  87. return;
  88. }
  89. webContents.__gt_injected = true;
  90. let hackCode = `
  91. (() => {
  92. function appendListener(node, eventType, fn = null) {
  93. node.addEventListener(eventType, (e) => {
  94. if (fn) fn(e);
  95. }, true);
  96. }
  97. let getLabelRoot = (gridRoot, className) => {
  98. for (let c of gridRoot.children) {
  99. if (c.className === className)
  100. return c;
  101. }
  102. return null;
  103. };
  104. let getPixel = (elem) => {
  105. return parseFloat(elem.style.transform.match(/(-?[0-9\.]+)/g)[0]);
  106. };
  107. let getWorldPos = (elem) => {
  108. return parseFloat(elem.innerText.replace(/,/g, ''));
  109. };
  110. let pixelToWorld = (labelRoot, pixel) => {
  111. let pmin = getPixel(labelRoot.firstChild);
  112. let pmax = getPixel(labelRoot.lastChild);
  113. let wmin = getWorldPos(labelRoot.firstChild);
  114. let wmax = getWorldPos(labelRoot.lastChild);
  115. return (pixel - pmin) * (wmax - wmin) / (pmax - pmin) + wmin;
  116. };
  117. let svgPosToWorld = (x, y) => {
  118. let gridRoot = document.getElementById('scene').shadowRoot.getElementById('sceneView').shadowRoot.getElementById('grid').shadowRoot;
  119. let vLabelRoot = getLabelRoot(gridRoot, 'vLabels');
  120. let hLabelRoot = getLabelRoot(gridRoot, 'hLabels');
  121. let worldX = pixelToWorld(hLabelRoot, x+4); // horizontal label offset = 4 (move rightward in svg)
  122. let worldY = pixelToWorld(vLabelRoot, y-15); // vertical label offset = -15 (move upward in svg)
  123. return [worldX, worldY];
  124. };
  125. let svgNode = null;
  126. let downX = 0;
  127. let downY = 0;
  128. let isDown = false;
  129. let postContextMenuMsg = () => {
  130. // if (cc.director.getScene().getName() != "reddot") {
  131. // return;
  132. // }
  133. let rect = svgNode.getBoundingClientRect();
  134. downX -= rect.left;
  135. downY -= rect.top;
  136. let worldX = 0;
  137. let worldY = 0;
  138. try {
  139. let arr = svgPosToWorld(downX, downY);
  140. worldX = arr[0];
  141. worldY = arr[1];
  142. } catch(error) {}
  143. Editor.Ipc.sendToMain('cc-ext-scene-menu:on-context-menu',
  144. {x: downX, y: downY, worldX: worldX, worldY: worldY}, null);
  145. };
  146. appendListener(document, 'mousedown', (e) => {
  147. if (e.button != 2)
  148. return;
  149. // check if inside svg view
  150. if (!svgNode)
  151. svgNode = document.getElementById('scene').shadowRoot.getElementById('sceneView').shadowRoot.getElementById('gizmosView').shadowRoot.getElementById('SvgjsSvg1000');
  152. if (!svgNode) {
  153. Editor.log('svg view not ready');
  154. return;
  155. }
  156. let rect = svgNode.getBoundingClientRect();
  157. if (e.pageX >= rect.left && e.pageX < rect.right && e.pageY >= rect.top && e.pageY < rect.bottom) {
  158. downX = e.pageX;
  159. downY = e.pageY;
  160. isDown = true;
  161. }
  162. });
  163. appendListener(document, 'mouseup', (e) => {
  164. if (e.button == 2 && isDown && e.pageX == downX && e.pageY == downY) {
  165. isDown = false;
  166. postContextMenuMsg();
  167. }
  168. });
  169. })();
  170. 1+2+3
  171. `;
  172. webContents.executeJavaScript(hackCode, function (result) {
  173. // result = 6
  174. });
  175. }
  176. module.exports = {
  177. load() {
  178. loadMenu();
  179. try {
  180. if (Editor.Window.main.nativeWin.webContents.__gt_injected) {
  181. // in case plugin if reloaded
  182. return;
  183. }
  184. } catch (error) {
  185. // usually happen when creator is just started and main window is not created
  186. Editor.log(error);
  187. }
  188. // todo: 如果插件是中途加载的,判断webContents如果就绪了就注入
  189. const electron = require('electron');
  190. let injectFn = injectContextMenu;
  191. electron.app.on('web-contents-created', (sender, webContents) => {
  192. webContents.on('dom-ready', (e) => {
  193. injectFn(e.sender);
  194. });
  195. });
  196. },
  197. unload() {
  198. },
  199. // register your ipc messages here
  200. messages: {
  201. 'create-node'() {
  202. Editor.Scene.callSceneScript('cc-ext-scene-menu', 'create-node', null, function (err) {
  203. // Editor.log('create-node finish');
  204. });
  205. },
  206. 'on-context-menu'(event, param) {
  207. param = param || { x: 0, y: 0, worldX: 0, worldY: 0 };
  208. _paramCache = param;
  209. if (_menuTemplateCache) {
  210. Editor.Window.main.popupMenu(_menuTemplateCache, param.x, param.y);
  211. }
  212. },
  213. 'custom-context-menu'() {
  214. Editor.Panel.open('cc-ext-scene-menu');
  215. },
  216. 'update-context-menu'() {
  217. loadMenu();
  218. },
  219. 'say-hello'(event, param) {
  220. Editor.log(`Hello! param: {worldX = ${param.worldX}, worldY = ${param.worldY}}, customParam = ${param.customParam}`);
  221. }
  222. },
  223. };