| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- define(function(require, exports, module) {
- var kity = require('../core/kity');
- var utils = require('../core/utils');
- var MinderNode = require('../core/node');
- var Command = require('../core/command');
- var Module = require('../core/module');
- // 矩形的变形动画定义
- var MoveToParentCommand = kity.createClass('MoveToParentCommand', {
- base: Command,
- execute: function(minder, nodes, parent) {
- var node;
- for (var i = 0; i < nodes.length; i++) {
- node = nodes[i];
- if (node.parent) {
- node.parent.removeChild(node);
- parent.appendChild(node);
- node.render();
- }
- }
- parent.expand();
- minder.select(nodes, true);
- }
- });
- var DropHinter = kity.createClass('DropHinter', {
- base: kity.Group,
- constructor: function() {
- this.callBase();
- this.rect = new kity.Rect();
- this.addShape(this.rect);
- },
- render: function(target) {
- this.setVisible(!!target);
- if (target) {
- this.rect
- .setBox(target.getLayoutBox())
- .setRadius(target.getStyle('radius') || 0)
- .stroke(
- target.getStyle('drop-hint-color') || 'yellow',
- target.getStyle('drop-hint-width') || 2
- );
- this.bringTop();
- }
- }
- });
- var OrderHinter = kity.createClass('OrderHinter', {
- base: kity.Group,
- constructor: function() {
- this.callBase();
- this.area = new kity.Rect();
- this.path = new kity.Path();
- this.addShapes([this.area, this.path]);
- },
- render: function(hint) {
- this.setVisible(!!hint);
- if (hint) {
- this.area.setBox(hint.area);
- this.area.fill(hint.node.getStyle('order-hint-area-color') || 'rgba(0, 255, 0, .5)');
- this.path.setPathData(hint.path);
- this.path.stroke(
- hint.node.getStyle('order-hint-path-color') || '#0f0',
- hint.node.getStyle('order-hint-path-width') || 1);
- }
- }
- });
- // 对拖动对象的一个替代盒子,控制整个拖放的逻辑,包括:
- // 1. 从节点列表计算出拖动部分
- // 2. 计算可以 drop 的节点,产生 drop 交互提示
- var TreeDragger = kity.createClass('TreeDragger', {
- constructor: function(minder) {
- this._minder = minder;
- this._dropHinter = new DropHinter();
- this._orderHinter = new OrderHinter();
- minder.getRenderContainer().addShapes([this._dropHinter, this._orderHinter]);
- },
- dragStart: function(position) {
- // 只记录开始位置,不马上开启拖放模式
- // 这个位置同时是拖放范围收缩时的焦点位置(中心)
- this._startPosition = position;
- },
- dragMove: function(position) {
- // 启动拖放模式需要最小的移动距离
- var DRAG_MOVE_THRESHOLD = 10;
- if (!this._startPosition) return;
- var movement = kity.Vector.fromPoints(this._dragPosition || this._startPosition, position);
- var minder = this._minder;
- this._dragPosition = position;
- if (!this._dragMode) {
- // 判断拖放模式是否该启动
- if (kity.Vector.fromPoints(this._dragPosition, this._startPosition).length() < DRAG_MOVE_THRESHOLD) {
- return;
- }
- if (!this._enterDragMode()) {
- return;
- }
- }
- for (var i = 0; i < this._dragSources.length; i++) {
- this._dragSources[i].setLayoutOffset(this._dragSources[i].getLayoutOffset().offset(movement));
- minder.applyLayoutResult(this._dragSources[i]);
- }
- if (!this._dropTest()) {
- this._orderTest();
- } else {
- this._renderOrderHint(this._orderSucceedHint = null);
- }
- },
- dragEnd: function() {
- this._startPosition = null;
- this._dragPosition = null;
- if (!this._dragMode) {
- return;
- }
- this._fadeDragSources(1);
- if (this._dropSucceedTarget) {
- this._dragSources.forEach(function(source) {
- source.setLayoutOffset(null);
- });
- this._minder.layout(-1);
- this._minder.execCommand('movetoparent', this._dragSources, this._dropSucceedTarget);
- } else if (this._orderSucceedHint) {
- var hint = this._orderSucceedHint;
- var index = hint.node.getIndex();
- var sourceIndexes = this._dragSources.map(function(source) {
- // 顺便干掉布局偏移
- source.setLayoutOffset(null);
- return source.getIndex();
- });
- var maxIndex = Math.max.apply(Math, sourceIndexes);
- var minIndex = Math.min.apply(Math, sourceIndexes);
- if (index < minIndex && hint.type == 'down') index++;
- if (index > maxIndex && hint.type == 'up') index--;
- hint.node.setLayoutOffset(null);
- this._minder.execCommand('arrange', index);
- this._renderOrderHint(null);
- } else {
- this._minder.fire('savescene');
- }
- this._minder.layout(300);
- this._leaveDragMode();
- this._minder.fire('contentchange');
- },
- // 进入拖放模式:
- // 1. 计算拖放源和允许的拖放目标
- // 2. 标记已启动
- _enterDragMode: function() {
- this._calcDragSources();
- if (!this._dragSources.length) {
- this._startPosition = null;
- return false;
- }
- this._fadeDragSources(0.5);
- this._calcDropTargets();
- this._calcOrderHints();
- this._dragMode = true;
- this._minder.setStatus('dragtree');
- return true;
- },
- // 从选中的节点计算拖放源
- // 并不是所有选中的节点都作为拖放源,如果选中节点中存在 A 和 B,
- // 并且 A 是 B 的祖先,则 B 不作为拖放源
- //
- // 计算过程:
- // 1. 将节点按照树高排序,排序后只可能是前面节点是后面节点的祖先
- // 2. 从后往前枚举排序的结果,如果发现枚举目标之前存在其祖先,
- // 则排除枚举目标作为拖放源,否则加入拖放源
- _calcDragSources: function() {
- this._dragSources = this._minder.getSelectedAncestors();
- },
- _fadeDragSources: function(opacity) {
- var minder = this._minder;
- this._dragSources.forEach(function(source) {
- source.getRenderContainer().setOpacity(opacity, 200);
- source.traverse(function(node) {
- if (opacity < 1) {
- minder.detachNode(node);
- } else {
- minder.attachNode(node);
- }
- }, true);
- });
- },
- // 计算拖放目标可以释放的节点列表(释放意味着成为其子树),存在这条限制规则:
- // - 不能拖放到拖放目标的子树上(允许拖放到自身,因为多选的情况下可以把其它节点加入)
- //
- // 1. 加入当前节点(初始为根节点)到允许列表
- // 2. 对于当前节点的每一个子节点:
- // (1) 如果是拖放目标的其中一个节点,忽略(整棵子树被剪枝)
- // (2) 如果不是拖放目标之一,以当前子节点为当前节点,回到 1 计算
- // 3. 返回允许列表
- //
- _calcDropTargets: function() {
- function findAvailableParents(nodes, root) {
- var availables = [],
- i;
- availables.push(root);
- root.getChildren().forEach(function(test) {
- for (i = 0; i < nodes.length; i++) {
- if (nodes[i] == test) return;
- }
- availables = availables.concat(findAvailableParents(nodes, test));
- });
- return availables;
- }
- this._dropTargets = findAvailableParents(this._dragSources, this._minder.getRoot());
- this._dropTargetBoxes = this._dropTargets.map(function(source) {
- return source.getLayoutBox();
- });
- },
- _calcOrderHints: function() {
- var sources = this._dragSources;
- var ancestor = MinderNode.getCommonAncestor(sources);
- // 只有一个元素选中,公共祖先是其父
- if (ancestor == sources[0]) ancestor = sources[0].parent;
- if (sources.length === 0 || ancestor != sources[0].parent) {
- this._orderHints = [];
- return;
- }
- var siblings = ancestor.children;
- this._orderHints = siblings.reduce(function(hint, sibling) {
- if (sources.indexOf(sibling) == -1) {
- hint = hint.concat(sibling.getOrderHint());
- }
- return hint;
- }, []);
- },
- _leaveDragMode: function() {
- this._dragMode = false;
- this._dropSucceedTarget = null;
- this._orderSucceedHint = null;
- this._renderDropHint(null);
- this._renderOrderHint(null);
- this._minder.rollbackStatus();
- },
- _drawForDragMode: function() {
- this._text.setContent(this._dragSources.length + ' items');
- this._text.setPosition(this._startPosition.x, this._startPosition.y + 5);
- this._minder.getRenderContainer().addShape(this);
- },
- /**
- * 通过 judge 函数判断 targetBox 和 sourceBox 的位置交叉关系
- * @param targets -- 目标节点
- * @param targetBoxMapper -- 目标节点与对应 Box 的映射关系
- * @param judge -- 判断函数
- * @returns {*}
- * @private
- */
- _boxTest: function(targets, targetBoxMapper, judge) {
- var sourceBoxes = this._dragSources.map(function(source) {
- return source.getLayoutBox();
- });
- var i, j, target, sourceBox, targetBox;
- judge = judge || function(intersectBox, sourceBox, targetBox) {
- return intersectBox && !intersectBox.isEmpty();
- };
- for (i = 0; i < targets.length; i++) {
- target = targets[i];
- targetBox = targetBoxMapper.call(this, target, i);
- for (j = 0; j < sourceBoxes.length; j++) {
- sourceBox = sourceBoxes[j];
- var intersectBox = sourceBox.intersect(targetBox);
- if (judge(intersectBox, sourceBox, targetBox)) {
- return target;
- }
- }
- }
- return null;
- },
- _dropTest: function() {
- this._dropSucceedTarget = this._boxTest(this._dropTargets, function(target, i) {
- return this._dropTargetBoxes[i];
- }, function(intersectBox, sourceBox, targetBox) {
- function area(box) {
- return box.width * box.height;
- }
- if (!intersectBox) return false;
- /*
- * Added by zhangbobell, 2015.9.8
- *
- * 增加了下面一行判断,修复了循环比较中 targetBox 为折叠节点时,intersetBox 面积为 0,
- * 而 targetBox 的 width 和 height 均为 0
- * 此时造成了满足以下的第二个条件而返回 true
- * */
- if (!area(intersectBox)) return false;
- // 面积判断,交叉面积大于其中的一半
- if (area(intersectBox) > 0.5 * Math.min(area(sourceBox), area(targetBox))) return true;
- // 有一个边完全重合的情况,也认为两个是交叉的
- if (intersectBox.width + 1 >= Math.min(sourceBox.width, targetBox.width)) return true;
- if (intersectBox.height + 1 >= Math.min(sourceBox.height, targetBox.height)) return true;
- return false;
- });
- this._renderDropHint(this._dropSucceedTarget);
- return !!this._dropSucceedTarget;
- },
- _orderTest: function() {
- this._orderSucceedHint = this._boxTest(this._orderHints, function(hint) {
- return hint.area;
- });
- this._renderOrderHint(this._orderSucceedHint);
- return !!this._orderSucceedHint;
- },
- _renderDropHint: function(target) {
- this._dropHinter.render(target);
- },
- _renderOrderHint: function(hint) {
- this._orderHinter.render(hint);
- },
- preventDragMove: function() {
- this._startPosition = null;
- }
- });
- Module.register('DragTree', function() {
- var dragger;
- return {
- init: function() {
- dragger = new TreeDragger(this);
- window.addEventListener('mouseup', function() {
- dragger.dragEnd();
- });
- },
- events: {
- 'normal.mousedown inputready.mousedown': function(e) {
- // 单选中根节点也不触发拖拽
- if (e.originEvent.button) return;
- if (e.getTargetNode() && e.getTargetNode() != this.getRoot()) {
- dragger.dragStart(e.getPosition());
- }
- },
- 'normal.mousemove dragtree.mousemove': function(e) {
- dragger.dragMove(e.getPosition());
- },
- 'normal.mouseup dragtree.beforemouseup': function(e) {
- dragger.dragEnd();
- //e.stopPropagation();
- e.preventDefault();
- },
- 'statuschange': function(e) {
- if (e.lastStatus == 'textedit' && e.currentStatus == 'normal') {
- dragger.preventDragMove();
- }
- }
- },
- commands: {
- 'movetoparent': MoveToParentCommand
- }
- };
- });
- });
|