main.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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.submenu) {
  45. item.submenu = generateMenuTemplate(c.submenu);
  46. } else {
  47. // unexpected
  48. }
  49. result.push(item);
  50. }
  51. return result;
  52. }
  53. function loadMenu() {
  54. const fs = require('fs');
  55. let configPath = Editor.Project.path + '/scene-menu-config.json';
  56. if (!fs.existsSync(configPath)) {
  57. // read default config
  58. configPath = Editor.url('packages://cc-ext-scene-menu/default-config.json');
  59. }
  60. fs.readFile(configPath, function (err, data) {
  61. if (err) {
  62. // file not exists
  63. return;
  64. }
  65. try {
  66. // Editor.log(`main.js read data: ${data}`);
  67. let config = JSON.parse(data);
  68. _menuTemplateCache = generateMenuTemplate(config);
  69. } catch (err) {
  70. // if any error occur, old template cache is not replaced
  71. } finally {
  72. }
  73. });
  74. }
  75. function injectContextMenu(webContents) {
  76. if (webContents.__gt_injected) {
  77. // already injected
  78. return;
  79. }
  80. if (Editor.Window.main && webContents != Editor.Window.main.nativeWin.webContents) {
  81. // not cc main app window
  82. return;
  83. }
  84. webContents.__gt_injected = true;
  85. let hackCode = `
  86. (() => {
  87. function appendListener(node, eventType, fn = null) {
  88. node.addEventListener(eventType, (e) => {
  89. if (fn) fn(e);
  90. }, true);
  91. }
  92. let getLabelRoot = (gridRoot, className) => {
  93. for (let c of gridRoot.children) {
  94. if (c.className === className)
  95. return c;
  96. }
  97. return null;
  98. };
  99. let getPixel = (elem) => {
  100. return parseFloat(elem.style.transform.match(/(-?[0-9\.]+)/g)[0]);
  101. };
  102. let getWorldPos = (elem) => {
  103. return parseFloat(elem.innerText.replace(/,/g, ''));
  104. };
  105. let pixelToWorld = (labelRoot, pixel) => {
  106. let pmin = getPixel(labelRoot.firstChild);
  107. let pmax = getPixel(labelRoot.lastChild);
  108. let wmin = getWorldPos(labelRoot.firstChild);
  109. let wmax = getWorldPos(labelRoot.lastChild);
  110. return (pixel - pmin) * (wmax - wmin) / (pmax - pmin) + wmin;
  111. };
  112. let svgPosToWorld = (x, y) => {
  113. let gridRoot = document.getElementById('scene').shadowRoot.getElementById('sceneView').shadowRoot.getElementById('grid').shadowRoot;
  114. let vLabelRoot = getLabelRoot(gridRoot, 'vLabels');
  115. let hLabelRoot = getLabelRoot(gridRoot, 'hLabels');
  116. let worldX = pixelToWorld(hLabelRoot, x+4); // horizontal label offset = 4 (move rightward in svg)
  117. let worldY = pixelToWorld(vLabelRoot, y-15); // vertical label offset = -15 (move upward in svg)
  118. return [worldX, worldY];
  119. };
  120. let svgNode = null;
  121. let downX = 0;
  122. let downY = 0;
  123. let isDown = false;
  124. let postContextMenuMsg = () => {
  125. let rect = svgNode.getBoundingClientRect();
  126. downX -= rect.left;
  127. downY -= rect.top;
  128. let worldX = 0;
  129. let worldY = 0;
  130. try {
  131. let arr = svgPosToWorld(downX, downY);
  132. worldX = arr[0];
  133. worldY = arr[1];
  134. } catch(error) {}
  135. Editor.Ipc.sendToMain('cc-ext-scene-menu:on-context-menu',
  136. {x: downX, y: downY, worldX: worldX, worldY: worldY}, null);
  137. };
  138. appendListener(document, 'mousedown', (e) => {
  139. if (e.button != 2)
  140. return;
  141. // check if inside svg view
  142. if (!svgNode)
  143. svgNode = document.getElementById('scene').shadowRoot.getElementById('sceneView').shadowRoot.getElementById('gizmosView').shadowRoot.getElementById('SvgjsSvg1000');
  144. if (!svgNode) {
  145. Editor.log('svg view not ready');
  146. return;
  147. }
  148. let rect = svgNode.getBoundingClientRect();
  149. if (e.pageX >= rect.left && e.pageX < rect.right && e.pageY >= rect.top && e.pageY < rect.bottom) {
  150. downX = e.pageX;
  151. downY = e.pageY;
  152. isDown = true;
  153. }
  154. });
  155. appendListener(document, 'mouseup', (e) => {
  156. if (e.button == 2 && isDown && e.pageX == downX && e.pageY == downY) {
  157. isDown = false;
  158. postContextMenuMsg();
  159. }
  160. });
  161. })();
  162. 1+2+3
  163. `;
  164. webContents.executeJavaScript(hackCode, function (result) {
  165. // result = 6
  166. });
  167. }
  168. module.exports = {
  169. load() {
  170. loadMenu();
  171. try {
  172. if (Editor.Window.main && Editor.Window.main.nativeWin.webContents.__gt_injected) {
  173. // in case plugin if reloaded
  174. return;
  175. }
  176. } catch (error) {
  177. // usually happen when creator is just started and main window is not created
  178. Editor.log(error);
  179. }
  180. // todo: 如果插件是中途加载的,判断webContents如果就绪了就注入
  181. const electron = require('electron');
  182. let injectFn = injectContextMenu;
  183. electron.app.on('web-contents-created', (sender, webContents) => {
  184. webContents.on('dom-ready', (e) => {
  185. injectFn(e.sender);
  186. });
  187. });
  188. },
  189. unload() {
  190. // let webContenst = Editor.Window.main.nativeWin.webContents;
  191. // if (webContenst.__gt_injected) {
  192. // // todo: removeEventListeners
  193. // webContenst.__gt_injected = false;
  194. // }
  195. // execute when package unloaded
  196. },
  197. // register your ipc messages here
  198. messages: {
  199. 'create-node'() {
  200. Editor.Scene.callSceneScript('cc-ext-scene-menu', 'create-node', null, function (err) {
  201. // Editor.log('create-node finish');
  202. });
  203. },
  204. 'on-context-menu'(event, param) {
  205. param = param || {x: 0, y: 0, worldX: 0, worldY: 0};
  206. _paramCache = param;
  207. if (_menuTemplateCache) {
  208. Editor.Window.main.popupMenu(_menuTemplateCache, param.x, param.y);
  209. }
  210. },
  211. 'custom-context-menu'() {
  212. Editor.Panel.open('cc-ext-scene-menu');
  213. },
  214. 'editGame'() {
  215. // Editor.log('打开导入关卡面板')
  216. Editor.Panel.open('cc-ext-scene-menu.editGame');
  217. },
  218. 'update-context-menu'() {
  219. loadMenu();
  220. },
  221. 'say-hello'(event, param) {
  222. Editor.log(`Hello! param: {worldX = ${param.worldX}, worldY = ${param.worldY}}, customParam = ${param.customParam}`);
  223. }
  224. },
  225. };