UNPKG

kityminder

Version:
584 lines (475 loc) 15.5 kB
/** * 布局支持池子管理 */ Utils.extend(KityMinder, { _layout: {}, registerLayout: function(name, layout) { KityMinder._layout[name] = layout; if (!KityMinder._defaultLayout) { KityMinder._defaultLayout = name; } }, getLayoutList: function() { return this._layout; }, getLayoutInstance: function(name) { var LayoutClass = KityMinder._layout[name]; if (!LayoutClass) throw new Error('Missing Layout: ' + name); var layout = new LayoutClass(); return layout; } }); /** * MinderNode 上的布局支持 */ kity.extendClass(MinderNode, { /** * 获得当前节点的布局名称 * * @return {String} */ getLayout: function() { var layout = this.getData('layout'); layout = layout || (this.isRoot() ? KityMinder._defaultLayout : this.parent.getLayout()); return layout; }, setLayout: function(name) { if (name) { if (name == 'inherit') { this.setData('layout'); } else { this.setData('layout', name); } } return this; }, layout: function(name, duration) { this.setLayout(name).getMinder().layout(duration); return this; }, getLayoutInstance: function() { return KityMinder.getLayoutInstance(this.getLayout()); }, getOrderHint: function(refer) { return this.parent.getLayoutInstance().getOrderHint(this); }, getExpandPosition: function() { return this.getLayoutInstance().getExpandPosition(); }, /** * 获取当前节点相对于父节点的布局变换 */ getLayoutTransform: function() { return this._layoutTransform || new kity.Matrix(); }, /** * 第一轮布局计算后,获得的全局布局位置 * * @return {[type]} [description] */ getGlobalLayoutTransformPreview: function() { var pMatrix = this.parent ? this.parent.getLayoutTransform() : new kity.Matrix(); var matrix = this.getLayoutTransform(); var offset = this.getLayoutOffset(); if (offset) { matrix.translate(offset.x, offset.y); } return pMatrix.merge(matrix); }, getLayoutPointPreview: function() { return this.getGlobalLayoutTransformPreview().transformPoint(new kity.Point()); }, /** * 获取节点相对于全局的布局变换 */ getGlobalLayoutTransform: function() { if (this._globalLayoutTransform) { return this._globalLayoutTransform; } else if (this.parent) { return this.parent.getGlobalLayoutTransform(); } else { return new kity.Matrix(); } }, /** * 设置当前节点相对于父节点的布局变换 */ setLayoutTransform: function(matrix) { this._layoutTransform = matrix; return this; }, /** * 设置当前节点相对于全局的布局变换(冗余优化) */ setGlobalLayoutTransform: function(matrix) { this.getRenderContainer().setMatrix(this._globalLayoutTransform = matrix); return this; }, setVertexIn: function(p) { this._vertexIn = p; }, setVertexOut: function(p) { this._vertexOut = p; }, getVertexIn: function() { return this._vertexIn || new kity.Point(); }, getVertexOut: function() { return this._vertexOut || new kity.Point(); }, getLayoutVertexIn: function() { return this.getGlobalLayoutTransform().transformPoint(this.getVertexIn()); }, getLayoutVertexOut: function() { return this.getGlobalLayoutTransform().transformPoint(this.getVertexOut()); }, setLayoutVectorIn: function(v) { this._layoutVectorIn = v; return this; }, setLayoutVectorOut: function(v) { this._layoutVectorOut = v; return this; }, getLayoutVectorIn: function() { return this._layoutVectorIn || new kity.Vector(); }, getLayoutVectorOut: function() { return this._layoutVectorOut || new kity.Vector(); }, getLayoutBox: function() { var matrix = this.getGlobalLayoutTransform(); return matrix.transformBox(this.getContentBox()); }, getLayoutPoint: function() { var matrix = this.getGlobalLayoutTransform(); return matrix.transformPoint(new kity.Point()); }, getLayoutOffset: function() { if (!this.parent) return new kity.Point(); // 影响当前节点位置的是父节点的布局 var data = this.getData('layout_' + this.parent.getLayout() + '_offset'); if (data) return new kity.Point(data.x, data.y); return new kity.Point(); }, setLayoutOffset: function(p) { if (!this.parent) return this; if (p && !this.hasLayoutOffset()) { var m = this.getLayoutTransform().m; p = p.offset(m.e, m.f); this.setLayoutTransform(null); } this.setData('layout_' + this.parent.getLayout() + '_offset', p ? { x: p.x, y: p.y } : null); return this; }, hasLayoutOffset: function() { return !!this.getData('layout_' + this.parent.getLayout() + '_offset'); }, resetLayoutOffset: function() { return this.setLayoutOffset(null); }, getLayoutRoot: function() { if (this.isLayoutRoot()) { return this; } return this.parent.getLayoutRoot(); }, isLayoutRoot: function() { return this.getData('layout') || this.isRoot(); } }); Minder.registerInit(function(options) { this.refresh(); }); kity.extendClass(Minder, { layout: function(duration) { this.getRoot().traverse(function(node) { // clear last results node.setLayoutTransform(null); }); function layoutNode(node, round) { // layout all children first // 剪枝:收起的节点无需计算 if (node.isExpanded() || true) { node.children.forEach(function(child) { layoutNode(child, round); }); } var layout = node.getLayoutInstance(); var childrenInFlow = node.getChildren().filter(function(child) { return !child.hasLayoutOffset(); }); layout.doLayout(node, childrenInFlow, round); } // 第一轮布局 layoutNode(this.getRoot(), 1); // 第二轮布局 layoutNode(this.getRoot(), 2); duration = duration ? 300 : 0; var minder = this; this.applyLayoutResult(this.getRoot(), duration).then(function() { minder.fire('layoutallfinish'); }); return this.fire('layout'); }, refresh: function(duration) { this.getRoot().renderTree(); this.layout(duration).fire('contentchange')._interactChange(); return this; }, applyLayoutResult: function(root, duration) { root = root || this.getRoot(); var me = this; var deffered = {}; var promise = new Promise(function(resolve, reject) { deffered.resolve = resolve; deffered.reject = reject; }); var complex = root.getComplex(); function consume() { if (!--complex) { deffered.resolve(); } } // 节点复杂度大于 100,关闭动画 if (complex > 200) duration = 0; function applyMatrix(node, matrix) { node.setGlobalLayoutTransform(matrix); me.fire('layoutapply', { node: node, matrix: matrix }); } function apply(node, pMatrix) { var matrix = node.getLayoutTransform().merge(pMatrix); var lastMatrix = node.getGlobalLayoutTransform() || new kity.Matrix(); var offset = node.getLayoutOffset(); matrix.translate(offset.x, offset.y); matrix.m.e = Math.round(matrix.m.e); matrix.m.f = Math.round(matrix.m.f); // 如果当前有动画,停止动画 if (node._layoutTimeline) { node._layoutTimeline.stop(); node._layoutTimeline = null; } // 如果要求以动画形式来更新,创建动画 if (duration) { node._layoutTimeline = new kity.Animator(lastMatrix, matrix, applyMatrix) .start(node, duration, 'ease') .on('finish', function() { //可能性能低的时候会丢帧,手动添加一帧 setTimeout(function() { applyMatrix(node, matrix); me.fire('layoutfinish', { node: node, matrix: matrix }); consume(); }, 150); }); } // 否则直接更新 else { applyMatrix(node, matrix); me.fire('layoutfinish', { node: node, matrix: matrix }); consume(); } for (var i = 0; i < node.children.length; i++) { apply(node.children[i], matrix); } } apply(root, root.parent ? root.parent.getGlobalLayoutTransform() : new kity.Matrix()); return promise; }, }); /** * @class Layout 布局基类,具体布局需要从该类派生 */ var Layout = kity.createClass('Layout', { /** * @abstract * * 子类需要实现的布局算法,该算法输入一个节点,排布该节点的子节点(相对父节点的变换) * * @param {MinderNode} node 需要布局的节点 * * @example * * doLayout: function(node) { * var children = node.getChildren(); * // layout calculation * children[i].setLayoutTransform(new kity.Matrix().translate(x, y)); * } */ doLayout: function(node) { throw new Error('Not Implement: Layout.doLayout()'); }, /** * 对齐指定的节点 * * @param {Array<MinderNode>} nodes 要对齐的节点 * @param {string} border 对齐边界,允许取值 left, right, top, bottom * */ align: function(nodes, border, offset) { var me = this; offset = offset || 0; nodes.forEach(function(node) { var tbox = me.getTreeBox([node]); var matrix = node.getLayoutTransform(); switch (border) { case 'left': return matrix.translate(offset - tbox.left, 0); case 'right': return matrix.translate(offset - tbox.right, 0); case 'top': return matrix.translate(0, offset - tbox.top); case 'bottom': return matrix.translate(0, offset - tbox.bottom); } }); }, stack: function(nodes, axis, distance) { var me = this; var position = 0; distance = distance || function(node, next, axis) { return node.getStyle({ x: 'margin-right', y: 'margin-bottom' }[axis]) + next.getStyle({ x: 'margin-left', y: 'margin-top' }[axis]); }; nodes.forEach(function(node, index, nodes) { var tbox = me.getTreeBox([node]); var size = { x: tbox.width, y: tbox.height }[axis]; var offset = { x: tbox.left, y: tbox.top }[axis]; var matrix = node.getLayoutTransform(); if (axis == 'x') { matrix.translate(position - offset, 0); } else { matrix.translate(0, position - offset); } position += size; if (nodes[index + 1]) position += distance(node, nodes[index + 1], axis); }); return position; }, move: function(nodes, dx, dy) { nodes.forEach(function(node) { node.getLayoutTransform().translate(dx, dy); }); }, /** * 工具方法:获取给点的节点所占的布局区域 * * @param {MinderNode[]} nodes 需要计算的节点 * * @return {Box} 计算结果 */ getBranchBox: function(nodes) { var box = new kity.Box(); var i, node, matrix, contentBox; for (i = 0; i < nodes.length; i++) { node = nodes[i]; matrix = node.getLayoutTransform(); contentBox = node.getContentBox(); box = box.merge(matrix.transformBox(contentBox)); } return box; }, /** * 工具方法:计算给定的节点的子树所占的布局区域 * * @param {MinderNode} nodes 需要计算的节点 * * @return {Box} 计算的结果 */ getTreeBox: function(nodes) { var i, node, matrix, treeBox; var g = KityMinder.Geometry; var box = { x: 0, y: 0, height: 0, width: 0 }; if (!(nodes instanceof Array)) nodes = [nodes]; for (i = 0; i < nodes.length; i++) { node = nodes[i]; matrix = node.getLayoutTransform(); treeBox = node.getContentBox(); if (node.isExpanded() && node.children.length) { treeBox = g.mergeBox(treeBox, this.getTreeBox(node.children)); } box = g.mergeBox(box, matrix.transformBox(treeBox)); } return box; }, getOrderHint: function(node) { return []; } }); var LayoutCommand = kity.createClass('LayoutCommand', { base: Command, execute: function(minder, name) { var nodes = minder.getSelectedNodes(); nodes.forEach(function(node) { node.layout(name); }); }, queryValue: function(minder) { var node = minder.getSelectedNode(); if (node) { return node.getData('layout'); } }, queryState: function(minder) { return minder.getSelectedNode() ? 0 : -1; } }); var ResetLayoutCommand = kity.createClass('ResetLayoutCommand', { base: Command, execute: function(minder, name) { var nodes = minder.getSelectedNodes(); if (!nodes.length) nodes = [minder.getRoot()]; nodes.forEach(function(node) { node.traverse(function(child) { child.resetLayoutOffset(); if (!child.isRoot()) { child.setData('layout', null); } }); }); minder.layout(300); }, enableReadOnly: true }); KityMinder.registerModule('LayoutModule', { commands: { 'layout': LayoutCommand, 'resetlayout': ResetLayoutCommand }, contextmenu: [{ command: 'resetlayout' }, { divider: true }], commandShortcutKeys: { 'resetlayout': 'Ctrl+Shift+L' } });