layout.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. define(function(require, exports, module) {
  2. var kity = require('./kity');
  3. var utils = require('./utils');
  4. var Minder = require('./minder');
  5. var MinderNode = require('./node');
  6. var MinderEvent = require('./event');
  7. var Command = require('./command');
  8. var _layouts = {};
  9. var _defaultLayout;
  10. function register(name, layout) {
  11. _layouts[name] = layout;
  12. _defaultLayout = _defaultLayout || name;
  13. }
  14. /**
  15. * @class Layout 布局基类,具体布局需要从该类派生
  16. */
  17. var Layout = kity.createClass('Layout', {
  18. /**
  19. * @abstract
  20. *
  21. * 子类需要实现的布局算法,该算法输入一个节点,排布该节点的子节点(相对父节点的变换)
  22. *
  23. * @param {MinderNode} node 需要布局的节点
  24. *
  25. * @example
  26. *
  27. * doLayout: function(node) {
  28. * var children = node.getChildren();
  29. * // layout calculation
  30. * children[i].setLayoutTransform(new kity.Matrix().translate(x, y));
  31. * }
  32. */
  33. doLayout: function(parent, children) {
  34. throw new Error('Not Implement: Layout.doLayout()');
  35. },
  36. /**
  37. * 对齐指定的节点
  38. *
  39. * @param {Array<MinderNode>} nodes 要对齐的节点
  40. * @param {string} border 对齐边界,允许取值 left, right, top, bottom
  41. *
  42. */
  43. align: function(nodes, border, offset) {
  44. var me = this;
  45. offset = offset || 0;
  46. nodes.forEach(function(node) {
  47. var tbox = me.getTreeBox([node]);
  48. var matrix = node.getLayoutTransform();
  49. switch (border) {
  50. case 'left':
  51. return matrix.translate(offset - tbox.left, 0);
  52. case 'right':
  53. return matrix.translate(offset - tbox.right, 0);
  54. case 'top':
  55. return matrix.translate(0, offset - tbox.top);
  56. case 'bottom':
  57. return matrix.translate(0, offset - tbox.bottom);
  58. }
  59. });
  60. },
  61. stack: function(nodes, axis, distance) {
  62. var me = this;
  63. var position = 0;
  64. distance = distance || function(node, next, axis) {
  65. return node.getStyle({
  66. x: 'margin-right',
  67. y: 'margin-bottom'
  68. }[axis]) + next.getStyle({
  69. x: 'margin-left',
  70. y: 'margin-top'
  71. }[axis]);
  72. };
  73. nodes.forEach(function(node, index, nodes) {
  74. var tbox = me.getTreeBox([node]);
  75. var size = {
  76. x: tbox.width,
  77. y: tbox.height
  78. }[axis];
  79. var offset = {
  80. x: tbox.left,
  81. y: tbox.top
  82. }[axis];
  83. var matrix = node.getLayoutTransform();
  84. if (axis == 'x') {
  85. matrix.translate(position - offset, 0);
  86. } else {
  87. matrix.translate(0, position - offset);
  88. }
  89. position += size;
  90. if (nodes[index + 1])
  91. position += distance(node, nodes[index + 1], axis);
  92. });
  93. return position;
  94. },
  95. move: function(nodes, dx, dy) {
  96. nodes.forEach(function(node) {
  97. node.getLayoutTransform().translate(dx, dy);
  98. });
  99. },
  100. /**
  101. * 工具方法:获取给点的节点所占的布局区域
  102. *
  103. * @param {MinderNode[]} nodes 需要计算的节点
  104. *
  105. * @return {Box} 计算结果
  106. */
  107. getBranchBox: function(nodes) {
  108. var box = new kity.Box();
  109. var i, node, matrix, contentBox;
  110. for (i = 0; i < nodes.length; i++) {
  111. node = nodes[i];
  112. matrix = node.getLayoutTransform();
  113. contentBox = node.getContentBox();
  114. box = box.merge(matrix.transformBox(contentBox));
  115. }
  116. return box;
  117. },
  118. /**
  119. * 工具方法:计算给定的节点的子树所占的布局区域
  120. *
  121. * @param {MinderNode} nodes 需要计算的节点
  122. *
  123. * @return {Box} 计算的结果
  124. */
  125. getTreeBox: function(nodes) {
  126. var i, node, matrix, treeBox;
  127. var box = new kity.Box();
  128. if (!(nodes instanceof Array)) nodes = [nodes];
  129. for (i = 0; i < nodes.length; i++) {
  130. node = nodes[i];
  131. matrix = node.getLayoutTransform();
  132. treeBox = node.getContentBox();
  133. if (node.isExpanded() && node.children.length) {
  134. treeBox = treeBox.merge(this.getTreeBox(node.children));
  135. }
  136. box = box.merge(matrix.transformBox(treeBox));
  137. }
  138. return box;
  139. },
  140. getOrderHint: function(node) {
  141. return [];
  142. }
  143. });
  144. Layout.register = register;
  145. Minder.registerInitHook(function(options) {
  146. this.refresh();
  147. });
  148. /**
  149. * 布局支持池子管理
  150. */
  151. utils.extend(Minder, {
  152. getLayoutList: function() {
  153. return _layouts;
  154. },
  155. getLayoutInstance: function(name) {
  156. var LayoutClass = _layouts[name];
  157. if (!LayoutClass) throw new Error('Missing Layout: ' + name);
  158. var layout = new LayoutClass();
  159. return layout;
  160. }
  161. });
  162. /**
  163. * MinderNode 上的布局支持
  164. */
  165. kity.extendClass(MinderNode, {
  166. /**
  167. * 获得当前节点的布局名称
  168. *
  169. * @return {String}
  170. */
  171. getLayout: function() {
  172. var layout = this.getData('layout');
  173. layout = layout || (this.isRoot() ? _defaultLayout : this.parent.getLayout());
  174. return layout;
  175. },
  176. setLayout: function(name) {
  177. if (name) {
  178. if (name == 'inherit') {
  179. this.setData('layout');
  180. } else {
  181. this.setData('layout', name);
  182. }
  183. }
  184. return this;
  185. },
  186. layout: function(name) {
  187. this.setLayout(name).getMinder().layout();
  188. return this;
  189. },
  190. getLayoutInstance: function() {
  191. return Minder.getLayoutInstance(this.getLayout());
  192. },
  193. getOrderHint: function(refer) {
  194. return this.parent.getLayoutInstance().getOrderHint(this);
  195. },
  196. /**
  197. * 获取当前节点相对于父节点的布局变换
  198. */
  199. getLayoutTransform: function() {
  200. return this._layoutTransform || new kity.Matrix();
  201. },
  202. /**
  203. * 第一轮布局计算后,获得的全局布局位置
  204. *
  205. * @return {[type]} [description]
  206. */
  207. getGlobalLayoutTransformPreview: function() {
  208. var pMatrix = this.parent ? this.parent.getLayoutTransform() : new kity.Matrix();
  209. var matrix = this.getLayoutTransform();
  210. var offset = this.getLayoutOffset();
  211. if (offset) {
  212. matrix = matrix.clone().translate(offset.x, offset.y);
  213. }
  214. return pMatrix.merge(matrix);
  215. },
  216. getLayoutPointPreview: function() {
  217. return this.getGlobalLayoutTransformPreview().transformPoint(new kity.Point());
  218. },
  219. /**
  220. * 获取节点相对于全局的布局变换
  221. */
  222. getGlobalLayoutTransform: function() {
  223. if (this._globalLayoutTransform) {
  224. return this._globalLayoutTransform;
  225. } else if (this.parent) {
  226. return this.parent.getGlobalLayoutTransform();
  227. } else {
  228. return new kity.Matrix();
  229. }
  230. },
  231. /**
  232. * 设置当前节点相对于父节点的布局变换
  233. */
  234. setLayoutTransform: function(matrix) {
  235. this._layoutTransform = matrix;
  236. return this;
  237. },
  238. /**
  239. * 设置当前节点相对于全局的布局变换(冗余优化)
  240. */
  241. setGlobalLayoutTransform: function(matrix) {
  242. this.getRenderContainer().setMatrix(this._globalLayoutTransform = matrix);
  243. return this;
  244. },
  245. setVertexIn: function(p) {
  246. this._vertexIn = p;
  247. },
  248. setVertexOut: function(p) {
  249. this._vertexOut = p;
  250. },
  251. getVertexIn: function() {
  252. return this._vertexIn || new kity.Point();
  253. },
  254. getVertexOut: function() {
  255. return this._vertexOut || new kity.Point();
  256. },
  257. getLayoutVertexIn: function() {
  258. return this.getGlobalLayoutTransform().transformPoint(this.getVertexIn());
  259. },
  260. getLayoutVertexOut: function() {
  261. return this.getGlobalLayoutTransform().transformPoint(this.getVertexOut());
  262. },
  263. setLayoutVectorIn: function(v) {
  264. this._layoutVectorIn = v;
  265. return this;
  266. },
  267. setLayoutVectorOut: function(v) {
  268. this._layoutVectorOut = v;
  269. return this;
  270. },
  271. getLayoutVectorIn: function() {
  272. return this._layoutVectorIn || new kity.Vector();
  273. },
  274. getLayoutVectorOut: function() {
  275. return this._layoutVectorOut || new kity.Vector();
  276. },
  277. getLayoutBox: function() {
  278. var matrix = this.getGlobalLayoutTransform();
  279. return matrix.transformBox(this.getContentBox());
  280. },
  281. getLayoutPoint: function() {
  282. var matrix = this.getGlobalLayoutTransform();
  283. return matrix.transformPoint(new kity.Point());
  284. },
  285. getLayoutOffset: function() {
  286. if (!this.parent) return new kity.Point();
  287. // 影响当前节点位置的是父节点的布局
  288. var data = this.getData('layout_' + this.parent.getLayout() + '_offset');
  289. if (data) return new kity.Point(data.x, data.y);
  290. return new kity.Point();
  291. },
  292. setLayoutOffset: function(p) {
  293. if (!this.parent) return this;
  294. this.setData('layout_' + this.parent.getLayout() + '_offset', p ? {
  295. x: p.x,
  296. y: p.y
  297. } : undefined);
  298. return this;
  299. },
  300. hasLayoutOffset: function() {
  301. return !!this.getData('layout_' + this.parent.getLayout() + '_offset');
  302. },
  303. resetLayoutOffset: function() {
  304. return this.setLayoutOffset(null);
  305. },
  306. getLayoutRoot: function() {
  307. if (this.isLayoutRoot()) {
  308. return this;
  309. }
  310. return this.parent.getLayoutRoot();
  311. },
  312. isLayoutRoot: function() {
  313. return this.getData('layout') || this.isRoot();
  314. }
  315. });
  316. /**
  317. * Minder 上的布局支持
  318. */
  319. kity.extendClass(Minder, {
  320. layout: function() {
  321. var duration = this.getOption('layoutAnimationDuration');
  322. this.getRoot().traverse(function(node) {
  323. // clear last results
  324. node.setLayoutTransform(null);
  325. });
  326. function layoutNode(node, round) {
  327. // layout all children first
  328. // 剪枝:收起的节点无需计算
  329. if (node.isExpanded() || true) {
  330. node.children.forEach(function(child) {
  331. layoutNode(child, round);
  332. });
  333. }
  334. var layout = node.getLayoutInstance();
  335. // var childrenInFlow = node.getChildren().filter(function(child) {
  336. // return !child.hasLayoutOffset();
  337. // });
  338. layout.doLayout(node, node.getChildren(), round);
  339. }
  340. // 第一轮布局
  341. layoutNode(this.getRoot(), 1);
  342. // 第二轮布局
  343. layoutNode(this.getRoot(), 2);
  344. var minder = this;
  345. this.applyLayoutResult(this.getRoot(), duration, function() {
  346. /**
  347. * 当节点>200, 不使用动画时, 此处逻辑变为同步逻辑, 外部minder.on事件无法
  348. * 被提前录入, 因此增加setTimeout
  349. * @author Naixor
  350. */
  351. setTimeout(function () {
  352. minder.fire('layoutallfinish');
  353. }, 0);
  354. });
  355. return this.fire('layout');
  356. },
  357. refresh: function() {
  358. this.getRoot().renderTree();
  359. this.layout().fire('contentchange')._interactChange();
  360. return this;
  361. },
  362. applyLayoutResult: function(root, duration, callback) {
  363. root = root || this.getRoot();
  364. var me = this;
  365. var complex = root.getComplex();
  366. function consume() {
  367. if (!--complex) {
  368. if (callback) {
  369. callback();
  370. }
  371. }
  372. }
  373. // 节点复杂度大于 100,关闭动画
  374. if (complex > 200) duration = 0;
  375. function applyMatrix(node, matrix) {
  376. node.setGlobalLayoutTransform(matrix);
  377. me.fire('layoutapply', {
  378. node: node,
  379. matrix: matrix
  380. });
  381. }
  382. function apply(node, pMatrix) {
  383. var matrix = node.getLayoutTransform().merge(pMatrix.clone());
  384. var lastMatrix = node.getGlobalLayoutTransform() || new kity.Matrix();
  385. var offset = node.getLayoutOffset();
  386. matrix.translate(offset.x, offset.y);
  387. matrix.m.e = Math.round(matrix.m.e);
  388. matrix.m.f = Math.round(matrix.m.f);
  389. // 如果当前有动画,停止动画
  390. if (node._layoutTimeline) {
  391. node._layoutTimeline.stop();
  392. node._layoutTimeline = null;
  393. }
  394. // 如果要求以动画形式来更新,创建动画
  395. if (duration) {
  396. node._layoutTimeline = new kity.Animator(lastMatrix, matrix, applyMatrix)
  397. .start(node, duration, 'ease')
  398. .on('finish', function() {
  399. //可能性能低的时候会丢帧,手动添加一帧
  400. setTimeout(function() {
  401. applyMatrix(node, matrix);
  402. me.fire('layoutfinish', {
  403. node: node,
  404. matrix: matrix
  405. });
  406. consume();
  407. }, 150);
  408. });
  409. }
  410. // 否则直接更新
  411. else {
  412. applyMatrix(node, matrix);
  413. me.fire('layoutfinish', {
  414. node: node,
  415. matrix: matrix
  416. });
  417. consume();
  418. }
  419. for (var i = 0; i < node.children.length; i++) {
  420. apply(node.children[i], matrix);
  421. }
  422. }
  423. apply(root, root.parent ? root.parent.getGlobalLayoutTransform() : new kity.Matrix());
  424. return this;
  425. }
  426. });
  427. module.exports = Layout;
  428. });