move.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. $axure.internal(function($ax) {
  2. var _move = {};
  3. $ax.move = _move;
  4. var widgetMoveInfo = {};
  5. //register and return move info, also create container for rootlayer if needed
  6. $ax.move.PrepareForMove = function (id, x, y, to, options, jobj, rootLayer, skipContainerForRootLayer) {
  7. var fixedInfo = jobj ? {} : $ax.dynamicPanelManager.getFixedInfo(id);
  8. var widget = $jobj(id);
  9. var query = $ax('#' + id);
  10. var isLayer = $ax.getTypeFromElementId(id) == $ax.constants.LAYER_TYPE;
  11. if(!rootLayer) {
  12. rootLayer = _move.getRootLayer(id);
  13. if (rootLayer && !skipContainerForRootLayer) {
  14. $ax.visibility.pushContainer(rootLayer, false);
  15. if (isLayer) widget = $ax.visibility.applyWidgetContainer(id, true);
  16. }
  17. }
  18. if (!jobj) jobj = widget;
  19. var horzProp = 'left';
  20. var vertProp = 'top';
  21. var offsetLocation = to ? query.offsetLocation() : undefined;
  22. var horzX = to ? x - offsetLocation.x : x;
  23. var vertY = to ? y - offsetLocation.y : y;
  24. //var horzX = to ? x - query.locRelativeIgnoreLayer(false) : x;
  25. //var vertY = to ? y - query.locRelativeIgnoreLayer(true) : y;
  26. if (fixedInfo.horizontal == 'right') {
  27. horzProp = 'right';
  28. horzX = to ? $(window).width() - x - $ax.getNumFromPx(jobj.css('right')) - query.width() : -x;
  29. var leftChanges = -horzX;
  30. } else if(fixedInfo.horizontal == 'center') {
  31. horzProp = 'margin-left';
  32. if (to) horzX = x - $(window).width() / 2;
  33. }
  34. if (fixedInfo.vertical == 'bottom') {
  35. vertProp = 'bottom';
  36. vertY = to ? $(window).height() - y - $ax.getNumFromPx(jobj.css('bottom')) - query.height() : -y;
  37. var topChanges = -vertY;
  38. } else if (fixedInfo.vertical == 'middle') {
  39. vertProp = 'margin-top';
  40. if (to) vertY = y - $(window).height() / 2;
  41. }
  42. //todo currently this always save the info, which is not needed for compound vector children and maybe some other cases
  43. //let's optimize it later, only register if registerid is valid..
  44. widgetMoveInfo[id] = {
  45. x: leftChanges === undefined ? horzX : leftChanges,
  46. y: topChanges === undefined ? vertY : topChanges,
  47. options: options
  48. };
  49. return {
  50. horzX: horzX,
  51. vertY: vertY,
  52. horzProp: horzProp,
  53. vertProp: vertProp,
  54. rootLayer: rootLayer,
  55. jobj: jobj
  56. };
  57. };
  58. $ax.move.GetWidgetMoveInfo = function() {
  59. return $.extend({}, widgetMoveInfo);
  60. };
  61. _move.getRootLayer = function (id) {
  62. var isLayer = $ax.getTypeFromElementId(id) == $ax.constants.LAYER_TYPE;
  63. var rootLayer = isLayer ? id : '';
  64. var parentIds = $ax('#' + id).getParents(true, '*')[0];
  65. for(var i = 0; i < parentIds.length; i++) {
  66. var parentId = parentIds[i];
  67. // Keep climbing up layers until you hit a non-layer. At that point you have your root layer
  68. if($ax.public.fn.IsLayer($ax.getTypeFromElementId(parentId))) rootLayer = parentId;
  69. else break;
  70. }
  71. return rootLayer;
  72. };
  73. $ax.move.MoveWidget = function (id, x, y, options, to, animationCompleteCallback, shouldFire, jobj, skipOnMoveEvent) {
  74. var moveInfo = $ax.move.PrepareForMove(id, x, y, to, options, jobj);
  75. $ax.drag.LogMovedWidgetForDrag(id, options.dragInfo);
  76. var object = $obj(id);
  77. if(object && $ax.public.fn.IsLayer(object.type)) {
  78. var childrenIds = $ax.public.fn.getLayerChildrenDeep(id, true);
  79. //don't push container when register moveinfo for child
  80. if(!skipOnMoveEvent) {
  81. for(var i = 0; i < childrenIds.length; i++) $ax.move.PrepareForMove(childrenIds[i], x, y, to, options, null, moveInfo.rootLayer, true);
  82. }
  83. }
  84. //if(!moveInfo) moveInfo = _getMoveInfo(id, x, y, to, options, jobj);
  85. jobj = moveInfo.jobj;
  86. _moveElement(id, options, animationCompleteCallback, shouldFire, jobj, moveInfo);
  87. if(skipOnMoveEvent) return;
  88. $ax.event.raiseSyntheticEvent(id, "onMove");
  89. if(childrenIds) {
  90. for(var i = 0; i < childrenIds.length; i++) $ax.event.raiseSyntheticEvent(childrenIds[i], 'onMove');
  91. }
  92. };
  93. var _trapScrollLoc = function(obj) {
  94. var objLoc = {
  95. x: obj.scrollLeft,
  96. y: obj.scrollTop
  97. }
  98. return function () {
  99. obj.scroll(objLoc.x, objLoc.y);
  100. };
  101. }
  102. var _moveElement = function (id, options, animationCompleteCallback, shouldFire, jobj, moveInfo){
  103. var cssStyles = {};
  104. if(!$ax.dynamicPanelManager.isPercentWidthPanel($obj(id))) cssStyles[moveInfo.horzProp] = '+=' + moveInfo.horzX;
  105. cssStyles[moveInfo.vertProp] = '+=' + moveInfo.vertY;
  106. $ax.visibility.moveMovedLocation(id, moveInfo.horzX, moveInfo.vertY);
  107. // I don't think root layer is necessary anymore after changes to layer container structure.
  108. // Wait to try removing it until more stable.
  109. var rootLayer = moveInfo.rootLayer;
  110. var query = $addAll(jobj, id);
  111. var completeCount = query.length;
  112. // use this hack to return the state scroll position to the original location because smthg unexpectedly sets it to(0,0). RP-2809
  113. var parentPanelStates = $ax('#' + id).getParents(true, 'state');
  114. var scrollPrevented = false;
  115. var completeAnimation = function () {
  116. // scroll hack, see above. RP-2809
  117. var preventNextStateScroll = function () {
  118. if(scrollPrevented || !parentPanelStates || !parentPanelStates.length) return;
  119. var state = document.getElementById(parentPanelStates[0]);
  120. if(!state) return;
  121. var trapScroll = _trapScrollLoc(state);
  122. var preventFunc = () => trapScroll();
  123. state.addEventListener("scroll", preventFunc, { once: true });
  124. scrollPrevented = true;
  125. }
  126. completeCount--;
  127. if(completeCount == 0 && rootLayer) $ax.visibility.popContainer(rootLayer, false);
  128. if(animationCompleteCallback) animationCompleteCallback();
  129. if(shouldFire) $ax.action.fireAnimationFromQueue(id, $ax.action.queueTypes.move);
  130. preventNextStateScroll();
  131. };
  132. if (options.easing === 'none') {
  133. //if not having this requestAnimationFrame causes performance issues,
  134. //add it back and move the above call to moveMovedLocation into it and the
  135. //query.animate calls below
  136. //window.requestAnimationFrame(function () {
  137. query.css(cssStyles);
  138. if (rootLayer) $ax.visibility.popContainer(rootLayer, false);
  139. if (animationCompleteCallback) animationCompleteCallback();
  140. //});
  141. //if this widget is inside a layer, we should just remove the layer from the queue
  142. if(shouldFire) $ax.action.fireAnimationFromQueue(id, $ax.action.queueTypes.move);
  143. } else if (options.trajectory === 'straight' || moveInfo.horzX === 0 || moveInfo.vertY === 0) {
  144. query.animate(cssStyles, {
  145. duration: options.duration, easing: options.easing, queue: false, complete: completeAnimation});
  146. } else {
  147. var initialHorzProp = $ax.getNumFromPx(query.css(moveInfo.horzProp));
  148. var initialVertProp = $ax.getNumFromPx(query.css(moveInfo.vertProp));
  149. var state = { parameter: 0 };
  150. var ellipseArcFunctionY = function(param) {
  151. return {
  152. x: initialHorzProp + (1.0 - Math.cos(param * Math.PI * 0.5)) * moveInfo.horzX,
  153. y: initialVertProp + Math.sin(param * Math.PI * 0.5) * moveInfo.vertY
  154. };
  155. };
  156. var ellipseArcFunctionX = function (param) {
  157. return {
  158. x: initialHorzProp + Math.sin(param * Math.PI * 0.5) * moveInfo.horzX,
  159. y: initialVertProp + (1.0 - Math.cos(param * Math.PI * 0.5)) * moveInfo.vertY
  160. };
  161. };
  162. var ellipseArcFunction = (moveInfo.horzX > 0) ^ (moveInfo.vertY > 0) ^ options.trajectory === 'arcClockwise'
  163. ? ellipseArcFunctionX : ellipseArcFunctionY;
  164. var inverseFunction = $ax.public.fn.inversePathLengthFunction(ellipseArcFunction);
  165. $(state).animate({ parameter: 1.0 }, {
  166. duration: options.duration, easing: options.easing, queue: false,
  167. step: function (now) {
  168. var newPos = ellipseArcFunction(inverseFunction(now));
  169. var changeFields = {};
  170. changeFields[moveInfo.horzProp] = newPos.x;
  171. changeFields[moveInfo.vertProp] = newPos.y;
  172. query.css(changeFields);
  173. },
  174. complete: completeAnimation});
  175. }
  176. // //moveinfo is used for moving 'with this'
  177. // var moveInfo = new Object();
  178. // moveInfo.x = horzX;
  179. // moveInfo.y = vertY;
  180. // moveInfo.options = options;
  181. // widgetMoveInfo[id] = moveInfo;
  182. };
  183. _move.nopMove = function(id, options) {
  184. var moveInfo = new Object();
  185. moveInfo.x = 0;
  186. moveInfo.y = 0;
  187. moveInfo.options = {};
  188. moveInfo.options.easing = 'none';
  189. moveInfo.options.duration = 0;
  190. widgetMoveInfo[id] = moveInfo;
  191. // Layer move using container now.
  192. var obj = $obj(id);
  193. if($ax.public.fn.IsLayer(obj.type)) if(options.onComplete) options.onComplete();
  194. $ax.event.raiseSyntheticEvent(id, "onMove");
  195. };
  196. //rotationDegree: total degree to rotate
  197. //centerPoint: the center of the circular path
  198. var _noRotateOnlyMove = function (id, moveDelta, rotatableMove, fireAnimationQueue, easing, duration, completionCallback) {
  199. moveDelta.x += rotatableMove.x;
  200. moveDelta.y += rotatableMove.y;
  201. if (moveDelta.x == 0 && moveDelta.y == 0) {
  202. if(fireAnimationQueue) {
  203. $ax.action.fireAnimationFromQueue(id, $ax.action.queueTypes.rotate);
  204. $ax.action.fireAnimationFromQueue(id, $ax.action.queueTypes.move);
  205. }
  206. if (completionCallback) completionCallback();
  207. } else {
  208. $jobj(id).animate({ top: '+=' + moveDelta.y, left: '+=' + moveDelta.x }, {
  209. duration: duration,
  210. easing: easing,
  211. queue: false,
  212. complete: function () {
  213. if(fireAnimationQueue) {
  214. $ax.action.fireAnimationFromQueue(id, $ax.action.queueTypes.move);
  215. $ax.action.fireAnimationFromQueue(id, $ax.action.queueTypes.rotate);
  216. }
  217. if (completionCallback) completionCallback();
  218. }
  219. });
  220. }
  221. }
  222. _move.circularMove = function (id, degreeDelta, centerPoint, moveDelta, rotatableMove, resizeOffset, options, fireAnimationQueue, completionCallback, willDoRotation) {
  223. var elem = $jobj(id);
  224. if(!willDoRotation) elem = $addAll(elem, id);
  225. var moveInfo = $ax.move.PrepareForMove(id, moveDelta.x, moveDelta.y, false, options);
  226. // If not rotating, still need to check moveDelta and may need to handle that.
  227. if (degreeDelta === 0) {
  228. _noRotateOnlyMove(id, moveDelta, rotatableMove, fireAnimationQueue, options.easing, options.duration, completionCallback);
  229. return;
  230. }
  231. var stepFunc = function(newDegree) {
  232. var deg = newDegree - rotation.degree;
  233. var widgetCenter = $ax('#' + id).offsetBoundingRect().centerPoint;
  234. //var widgetCenter = $ax.public.fn.getWidgetBoundingRect(id).centerPoint;
  235. //console.log("widget center of " + id + " x " + widgetCenter.x + " y " + widgetCenter.y);
  236. var widgetNewCenter = $axure.fn.getPointAfterRotate(deg, widgetCenter, centerPoint);
  237. // Start by getting the move not related to rotation, and make sure to update center point to move with it.
  238. var ratio = deg / degreeDelta;
  239. var xdelta = (moveDelta.x + rotatableMove.x) * ratio;
  240. var ydelta = (moveDelta.y + rotatableMove.y) * ratio;
  241. if(resizeOffset) {
  242. var resizeShift = {};
  243. resizeShift.x = resizeOffset.x * ratio;
  244. resizeShift.y = resizeOffset.y * ratio;
  245. $axure.fn.getPointAfterRotate(rotation.degree, resizeShift, { x: 0, y: 0 });
  246. xdelta += resizeShift.x;
  247. ydelta += resizeShift.y;
  248. }
  249. centerPoint.x += xdelta;
  250. centerPoint.y += ydelta;
  251. // Now for the move that is rotatable, it must be rotated
  252. rotatableMove = $axure.fn.getPointAfterRotate(deg, rotatableMove, { x: 0, y: 0 });
  253. // Now add in circular move to the mix.
  254. xdelta += widgetNewCenter.x - widgetCenter.x;
  255. ydelta += widgetNewCenter.y - widgetCenter.y;
  256. $ax.visibility.moveMovedLocation(id, xdelta, ydelta);
  257. if(xdelta < 0) elem.css('left', '-=' + -xdelta);
  258. else if(xdelta > 0) elem.css('left', '+=' + xdelta);
  259. if(ydelta < 0) elem.css('top', '-=' + -ydelta);
  260. else if(ydelta > 0) elem.css('top', '+=' + ydelta);
  261. };
  262. var onComplete = function() {
  263. if(fireAnimationQueue) $ax.action.fireAnimationFromQueue(id, $ax.action.queueTypes.move);
  264. if(completionCallback) completionCallback();
  265. if(moveInfo.rootLayer) $ax.visibility.popContainer(moveInfo.rootLayer, false);
  266. var isPercentWidthPanel = $ax.dynamicPanelManager.isPercentWidthPanel($obj(id));
  267. if(isPercentWidthPanel) {
  268. $ax.dynamicPanelManager.updatePanelPercentWidth(id);
  269. $ax.dynamicPanelManager.updatePanelContentPercentWidth(id);
  270. }
  271. if(elem.css('position') == 'fixed') {
  272. if(!isPercentWidthPanel) elem.css('left', '');
  273. elem.css('top', '');
  274. }
  275. };
  276. var rotation = { degree: 0 };
  277. if(!options.easing || options.easing === 'none' || options.duration <= 0) {
  278. stepFunc(degreeDelta);
  279. onComplete();
  280. } else {
  281. $(rotation).animate({ degree: degreeDelta }, {
  282. duration: options.duration,
  283. easing: options.easing,
  284. queue: false,
  285. step: stepFunc,
  286. complete: onComplete
  287. });
  288. }
  289. };
  290. //rotate a widget by degree, center is 50% 50%
  291. _move.rotate = function (id, degree, easing, duration, to, shouldFire, completionCallback) {
  292. var currentDegree = _move.getRotationDegree(id);
  293. if(to) degree = degree - currentDegree;
  294. if(degree === 0) {
  295. if (shouldFire) $ax.action.fireAnimationFromQueue(id, $ax.action.queueTypes.rotate);
  296. return;
  297. }
  298. var query = $jobj(id);
  299. var stepFunc = function(now) {
  300. var degreeDelta = now - rotation.degree;
  301. var newDegree = currentDegree + degreeDelta;
  302. query.css($ax.public.fn.setTransformHowever("rotate(" + newDegree + "deg)"));
  303. currentDegree = newDegree;
  304. };
  305. var onComplete = function() {
  306. if(shouldFire) {
  307. $ax.action.fireAnimationFromQueue($ax.public.fn.compoundIdFromComponent(id), $ax.action.queueTypes.rotate);
  308. }
  309. if(completionCallback) completionCallback();
  310. $ax.annotation.adjustIconLocation(id);
  311. };
  312. var rotation = { degree: 0 };
  313. $ax.visibility.setRotatedAngle(id, currentDegree + degree);
  314. //if no animation, setting duration to 1, to prevent RangeError in rotation loops without animation
  315. if(!easing || easing === 'none' || duration <= 0) {
  316. stepFunc(degree);
  317. onComplete();
  318. } else {
  319. $(rotation).animate({ degree: degree }, {
  320. duration: duration,
  321. easing: easing,
  322. queue: false,
  323. step: stepFunc,
  324. complete: onComplete
  325. });
  326. }
  327. };
  328. _move.compoundRotateAround = function (id, degreeDelta, centerPoint, moveDelta, rotatableMove, resizeOffset, easing, duration, fireAnimationQueue, completionCallback) {
  329. if (degreeDelta === 0) {
  330. _noRotateOnlyMove($ax.public.fn.compoundIdFromComponent(id), moveDelta, rotatableMove, fireAnimationQueue, easing, duration, completionCallback, $ax.action.queueTypes.rotate);
  331. return;
  332. }
  333. var elem = $jobj(id);
  334. var rotation = { degree: 0 };
  335. if (!easing || easing === 'none' || duration <= 0) {
  336. duration = 1;
  337. easing = 'linear'; //it doesn't matter anymore here...
  338. }
  339. var originalWidth = $ax.getNumFromPx(elem.css('width'));
  340. var originalHeight = $ax.getNumFromPx(elem.css('height'));
  341. var originalLeft = $ax.getNumFromPx(elem.css('left'));
  342. var originalTop = $ax.getNumFromPx(elem.css('top'));
  343. $(rotation).animate({ degree: degreeDelta }, {
  344. duration: duration,
  345. easing: easing,
  346. queue: false,
  347. step: function (newDegree) {
  348. var transform = $ax.public.fn.transformFromElement(elem[0]);
  349. var originalCenter = { x: originalLeft + 0.5 * originalWidth, y: originalTop + 0.5 * originalHeight};
  350. var componentCenter = { x: originalCenter.x + transform[4], y: originalCenter.y + transform[5] };
  351. var deg = newDegree - rotation.degree;
  352. var ratio = deg / degreeDelta;
  353. var xdelta = (moveDelta.x + rotatableMove.x) * ratio;
  354. var ydelta = (moveDelta.y + rotatableMove.y) * ratio;
  355. if (resizeOffset) {
  356. var resizeShift = {};
  357. resizeShift.x = resizeOffset.x * ratio;
  358. resizeShift.y = resizeOffset.y * ratio;
  359. $axure.fn.getPointAfterRotate(rotation.degree, resizeShift, { x: 0, y: 0 });
  360. xdelta += resizeShift.x;
  361. ydelta += resizeShift.y;
  362. }
  363. var rotationMatrix = $ax.public.fn.rotationMatrix(deg);
  364. var compositionTransform = $ax.public.fn.matrixMultiplyMatrix(rotationMatrix,
  365. { m11: transform[0], m21: transform[1], m12: transform[2], m22: transform[3] });
  366. //console.log("widget center of " + id + " x " + widgetCenter.x + " y " + widgetCenter.y);
  367. var widgetNewCenter = $axure.fn.getPointAfterRotate(deg, componentCenter, centerPoint);
  368. var newMatrix = $ax.public.fn.matrixString(compositionTransform.m11, compositionTransform.m21, compositionTransform.m12, compositionTransform.m22,
  369. widgetNewCenter.x - originalCenter.x + xdelta, widgetNewCenter.y - originalCenter.y + ydelta);
  370. elem.css($ax.public.fn.setTransformHowever(newMatrix));
  371. },
  372. complete: function () {
  373. if (fireAnimationQueue) {
  374. $ax.action.fireAnimationFromQueue(elem.parent()[0].id, $ax.action.queueTypes.rotate);
  375. }
  376. if(completionCallback) completionCallback();
  377. }
  378. });
  379. };
  380. _move.getRotationDegreeFromElement = function(element) {
  381. if(element == null) return NaN;
  382. var transformString = element.style['transform'] ||
  383. element.style['-o-transform'] ||
  384. element.style['-ms-transform'] ||
  385. element.style['-moz-transform'] ||
  386. element.style['-webkit-transform'];
  387. if(transformString) {
  388. var rotateRegex = /rotate\(([-?0-9]+)deg\)/;
  389. var degreeMatch = rotateRegex.exec(transformString);
  390. if(degreeMatch && degreeMatch[1]) return parseFloat(degreeMatch[1]);
  391. }
  392. if(window.getComputedStyle) {
  393. var st = window.getComputedStyle(element, null);
  394. } else {
  395. console.log('rotation is not supported for ie 8 and below in this version of axure rp');
  396. return 0;
  397. }
  398. var tr = st.getPropertyValue("transform") ||
  399. st.getPropertyValue("-o-transform") ||
  400. st.getPropertyValue("-ms-transform") ||
  401. st.getPropertyValue("-moz-transform") ||
  402. st.getPropertyValue("-webkit-transform");
  403. if(!tr || tr === 'none') return 0;
  404. var values = tr.split('(')[1];
  405. values = values.split(')')[0],
  406. values = values.split(',');
  407. var a = values[0];
  408. var b = values[1];
  409. var radians = Math.atan2(b, a);
  410. if(radians < 0) {
  411. radians += (2 * Math.PI);
  412. }
  413. return radians * (180 / Math.PI);
  414. };
  415. _move.getRotationDegree = function(elementId) {
  416. if($ax.public.fn.IsLayer($obj(elementId).type)) {
  417. return $jobj(elementId).data('layerDegree');
  418. }
  419. return _move.getRotationDegreeFromElement(document.getElementById(elementId));
  420. }
  421. });