Browse Source

Implemented keyframes line drag.

Ievgen Naida 6 năm trước cách đây
mục cha
commit
acccbf832b
1 tập tin đã thay đổi với 223 bổ sung109 xóa
  1. 223 109
      animation-timeline.js

+ 223 - 109
animation-timeline.js

@@ -190,9 +190,7 @@ var animationTimeline = function (window, document) {
 
 	this.initialize = function (options, lanes) {
 		var timeLine = {
-			ms: 3503,
-			width: 5,
-			isDrag: false,
+			ms: 0,
 		}
 
 		// Merge options with the default:
@@ -262,41 +260,79 @@ var animationTimeline = function (window, document) {
 
 
 		// Check whether we can drag something here.
-		function getDragableObject(pos) {
+		function getDraggable(pos) {
 
-			let dragable = null;
-			iterateKeyframes(function (keyframe, keyframeIndex, lane, laneIndex) {
+			// few extra pixels to select items:
+			let helperSelector = 10;
+			let draggable = null;
+
+			iterateKeyframes(function (keyframe, keyframeIndex, lane, laneIndex, isNextLane) {
 				let keyframePos = getKeyframePosition(keyframe, laneIndex);
 				if (keyframePos) {
 					const dist = getDistance(keyframePos.x, keyframePos.y, pos.x, pos.y);
-					if (dist <= keyframePos.size + 2) {
-						canvas.style.cursor = "move";
-						if (!dragable) {
-							dragable = {
+					if (dist <= keyframePos.size + helperSelector) {
+						if (!draggable) {
+							draggable = {
 								obj: keyframe,
+								pos: pos,
+								ms: keyframe.ms,
 								type: 'keyframe',
 								distance: dist
 							}
-						} else if (dist <= dragable.distance) {
-							dragable.obj = keyframe;
+						} else if (dist <= draggable.distance) {
+							draggable.obj = keyframe;
+							draggable.ms = keyframe.ms;
 						}
 					}
 				}
 			})
 
-			if (dragable) {
-				return dragable;
+			if (draggable) {
+				return draggable;
+			}
+
+			// Return keyframes lanes:
+			const lanesSizes = getLanesSizes();
+			if (lanesSizes && lanesSizes.find) {
+				let foundOverlap = lanesSizes.find(function lanesSizesIterator(laneSize) {
+					if (!laneSize || !laneSize.keyframes) {
+						return false;
+					}
+
+					let laneOverlaped = isOverlap(pos.x, pos.y, laneSize.keyframes);
+					return laneOverlaped;
+				});
+
+				if (foundOverlap) {
+					draggable = {
+						obj: foundOverlap,
+						pos: pos,
+						type: 'lane'
+					}
+
+					draggable.ms = mousePosToMs(pos.x, true);
+
+					if (foundOverlap && foundOverlap.keyframes) {
+						if (foundOverlap.lane && foundOverlap.lane.keyframes) {
+							draggable.selectedItems = foundOverlap.lane.keyframes;
+						}
+
+						let firstMs = foundOverlap.keyframes.from;
+						let snapped = snapMs(firstMs);
+						// get snapped mouse pos based on the first keynode.
+						draggable.ms += firstMs - snapped;
+					}
+
+					return draggable;
+				}
 			}
 
 			// Check whether we can drag timeline.
 			var timeLinePos = msToPx(timeLine.ms);
-			if (pos.x >= timeLinePos - timeLine.width / 2 && pos.x <= timeLinePos + timeLine.width / 2) {
-				canvas.style.cursor = "move";
-				timeLine.selected = true;
+			let width = Math.max(((options.timelineThicknessPx || 1) * pixelRatio), options.timelineCapWidthPx * pixelRatio || 1) + helperSelector;
+			if (pos.x >= timeLinePos - width / 2 && pos.x <= timeLinePos + width / 2) {
 				return { obj: timeLine, type: "timeline" };
 			}
-
-			canvas.style.cursor = 'default';
 		}
 
 		canvas.addEventListener("wheel", function (event) {
@@ -351,19 +387,19 @@ var animationTimeline = function (window, document) {
 			currentPos = startPos;
 			startPos.scrollLeft = scrollContainer.scrollLeft;
 			startPos.scrollTop = scrollContainer.scrollTo;
-			drag = getDragableObject(currentPos);
+			drag = getDraggable(currentPos);
 			// Select keyframes on mouse down
-			if (drag && drag.type == 'keyframe') {
-				drag.startedWithCtrl = args.ctrlKey;
-				drag.startedWithShiftKey = args.shiftKey;
-				// get all related selected keyframes if we are selecting one.
-				if (!drag.obj.selected && !args.ctrlKey && !args.shiftKey) {
-					performSelection(true, drag.obj, 'keyframe');
-				}
+			if (drag) {
+				if (drag.type == 'keyframe') {
+					drag.startedWithCtrl = args.ctrlKey;
+					drag.startedWithShiftKey = args.shiftKey;
+					// get all related selected keyframes if we are selecting one.
+					if (!drag.obj.selected && !args.ctrlKey && !args.shiftKey) {
+						performSelection(true, drag.obj, 'keyframe');
+					}
 
-				drag.selectedItems = getSelectedKeyframes();
-				// redraw already performed.
-				return;
+					drag.selectedItems = getSelectedKeyframes();
+				}
 			}
 
 			redraw();
@@ -376,7 +412,6 @@ var animationTimeline = function (window, document) {
 				selectionRect.draw = true;
 			}
 
-			console.log('x: ' + currentPos.x + '. ms:' + pxToMS(currentPos.x));//+ '. px:' + msToPx(ms));
 			if (startPos) {
 				if (args.buttons == 1) {
 					scrollByMouse(currentPos.x);
@@ -386,11 +421,10 @@ var animationTimeline = function (window, document) {
 						let isChanged = false;
 						if (drag.type == 'timeline') {
 							isChanged = setTime(convertedMs);
-						} else if (drag.type == 'keyframe') {
+						} else if (drag.type == 'keyframe' || drag.type == 'lane') {
 
 							if (drag.selectedItems) {
-
-								let offset = Math.floor(convertedMs - drag.obj.ms);
+								let offset = Math.floor(convertedMs - drag.ms);
 								if (Math.abs(offset) > 0) {
 									// dont allow to move less than zero.
 									drag.selectedItems.forEach(function (p) {
@@ -411,9 +445,6 @@ var animationTimeline = function (window, document) {
 										// dont allow to move less than zero.
 										drag.selectedItems.forEach(function (p) {
 											let ms = p.ms + offset;
-											if (ms < 0) {
-
-											}
 
 											let toSet = p.ms + offset;
 											if (p.ms != toSet) {
@@ -421,8 +452,14 @@ var animationTimeline = function (window, document) {
 												isChanged = true;
 											}
 										});
+
+									}
+
+									if (isChanged) {
+										drag.ms += offset;
 									}
 								}
+
 							}
 						}
 
@@ -441,9 +478,21 @@ var animationTimeline = function (window, document) {
 				}
 				redraw();
 			} else {
-				// TODO: used to change mouse cursor. 
-				// Should be changed.
-				getDragableObject(currentPos);
+				let draggable = getDraggable(currentPos);
+				if (draggable) {
+					if (draggable.type == 'lane') {
+						canvas.style.cursor = "ew-resize";
+					}
+					else if (draggable.type == 'keyframe') {
+						canvas.style.cursor = "pointer";
+					}
+					else {
+						canvas.style.cursor = "ew-resize";
+					}
+				}
+				else {
+					canvas.style.cursor = 'default';
+				}
 			}
 		}, false);
 
@@ -507,22 +556,30 @@ var animationTimeline = function (window, document) {
 
 		selectedKeyframes = [];
 
-		function performSelection(value, selector, mode, ignoreOthers) {
-			if (value === undefined) {
-				value = true;
+		/**
+		 * Do the selection.
+		 * @param {boolean} isSelected 
+		 * @param {object} selector can be retangle or keyframe object.
+		 * @param {string} mode selector mode. keyframe | rectrangle | all  
+		 * @param {boolean} ignoreOthers value indicating whether all other object should be reversed. 
+		 * @return isChanged
+		 */
+		function performSelection(isSelected, selector, mode, ignoreOthers) {
+			if (isSelected === undefined) {
+				isSelected = true;
 			}
-			console.log('selection' + mode);
+
 			let deselectionMode = false;
 			if (!mode) {
 				mode = 'all';
 			}
 
 			if (mode == 'all') {
-				if (!value) {
-					value = false;
+				if (!isSelected) {
+					isSelected = false;
 				}
 
-				deselectionMode = value;
+				deselectionMode = isSelected;
 			}
 
 			selectedKeyframes.length = 0;
@@ -534,8 +591,8 @@ var animationTimeline = function (window, document) {
 					if ((mode == 'keyframe' && selector == keyframe) ||
 						(mode == 'rectangle' && selector && isOverlap(keyframePos.x, keyframePos.y, selector))) {
 						atleastOneSelected = true;
-						if (keyframe.selected != value) {
-							keyframe.selected = value;
+						if (keyframe.selected != isSelected) {
+							keyframe.selected = isSelected;
 							isChanged = true;
 						}
 
@@ -554,10 +611,9 @@ var animationTimeline = function (window, document) {
 
 			if (isChanged) {
 				onKeyframesSelected(selectedKeyframes);
-				redraw();
 			}
 
-			return selectedKeyframes;
+			return isChanged;
 		}
 
 		function iterateKeyframes(callback) {
@@ -565,15 +621,19 @@ var animationTimeline = function (window, document) {
 				return false;
 			}
 
+			let nextLane = false;
 			lanes.forEach(function lanesIterator(lane, index) {
 				if (!lane || !lane.keyframes || !lane.keyframes.forEach || lane.keyframes.length <= 0) {
 					return;
 				}
 
+				nextLane = true;
 				lane.keyframes.forEach(function keyframesIterator(keyframe, keyframeIndex) {
 					if (callback && keyframe) {
-						callback(keyframe, keyframeIndex, lane, index);
+						callback(keyframe, keyframeIndex, lane, index, nextLane);
 					}
+
+					nextLane = false;
 				});
 			});
 		}
@@ -826,77 +886,112 @@ var animationTimeline = function (window, document) {
 			ctx.restore();
 		}
 
-		function drawLanes() {
-			if (!lanes || !lanes.forEach || lanes.length <= 0) {
-				return false;
+		function getLanesSizes() {
+			let lanes = []
+			if (!options.laneHeightPx) {
+				console.log('laneHeightPx should be set.');
+				return lanes;
 			}
 
-			ctx.save();
-			// Draw lane for each control
-			lanes.forEach(function (lane, index) {
-				if (lane.selected && options.selectedLaneColor) {
-					ctx.fillStyle = options.selectedLaneColor;
-				} else if (index % 2 != 0 && options.useAlternateLaneColor) {
-					ctx.fillStyle = options.alternateLaneColor || options.laneColor;
-				} else {
-					ctx.fillStyle = options.laneColor;
-				}
+			let prevLane = null;
+			let laneSize = null;
+			iterateKeyframes(function (keyframe, index, lane, laneIndex, isNextLane) {
+				if (isNextLane) {
+
+					let laneY = getLanePosition(laneIndex);
+					prevLane = laneSize;
+					laneSize = {
+						x: 0,
+						y: laneY,
+						w: canvas.clientWidth,
+						h: options.laneHeightPx,
+						lane: lane,
+						index: laneIndex,
+						keyframes: {
+							from: null,
+							to: null,
+							count: lane.keyframes ? lane.keyframes.length : 0,
+						}
+					};
 
+					lanes.push(laneSize);
 
-				let laneY = getLanePosition(index);
-				if (ctx.fillStyle) {
-					ctx.fillRect(0, laneY, canvas.clientWidth, options.laneHeightPx);
+					// Draw keyframes lane:
+					if (prevLane && !isNaN(prevLane.keyframes.from) && !isNaN(prevLane.keyframes.to)) {
+						// draw keyframes lane.
+						var fromPos = getSharp(msToPx(prevLane.keyframes.from))
+						var toPos = getSharp(msToPx(prevLane.keyframes.to));
+						const laneHeight = getKeyframeLaneHeight(lane, prevLane.y);
+
+						if (fromPos <= 0) {
+							fromPos = 0;
+						}
+
+						prevLane.keyframes.x = fromPos;
+						prevLane.keyframes.y = laneHeight.y;
+						prevLane.keyframes.w = getDistance(fromPos, toPos);
+						prevLane.keyframes.h = laneHeight.h;
+					}
 				}
 
-				if (!lane || !lane.keyframes || !lane.keyframes.forEach || lane.keyframes.length <= 0) {
-					return;
+				if (laneSize.keyframes && keyframe && !isNaN(keyframe.ms)) {
+					let size = laneSize.keyframes;
+					if (size.from == null) {
+						size.from = keyframe.ms;
+					} else {
+						size.from = Math.min(keyframe.ms, size.from);
+					}
+
+					if (size.to == null) {
+						size.to = keyframe.ms
+					} else {
+						size.to = Math.max(keyframe.ms, size.to);
+					}
 				}
+			});
 
-				// Draw keyframes lanes
-				if (options.keyframesLaneColor) {
-					var from = null;
-					var to = null;
-					lane.keyframes.forEach(function (keyframe) {
-						if (keyframe && !isNaN(keyframe.ms)) {
-							if (from == null) {
-								from = keyframe.ms;
-							} else {
-								from = Math.min(keyframe.ms, from);
-							}
+			return lanes;
+		}
 
-							if (to == null) {
-								to = keyframe.ms
-							} else {
-								to = Math.max(keyframe.ms, to);
-							}
-						}
-					});
+		function drawLanes() {
+			if (!lanes || !lanes.forEach || lanes.length <= 0) {
+				return false;
+			}
 
-					// draw keyframes lane.
-					var fromPos = getSharp(msToPx(from))
-					var toPos = getSharp(msToPx(to));
-					ctx.fillStyle = options.keyframesLaneColor;
-					let keyframeLaneHeight = lane.keyframesLaneSizePx || options.keyframesLaneSizePx;
-					if (isNaN(keyframeLaneHeight)) {
-						keyframeLaneHeight = 'auto';
-					}
+			let lanesSizes = getLanesSizes();
+			if (lanesSizes && lanesSizes.forEach) {
+				ctx.save();
+				lanesSizes.forEach(function lanesSizesIterator(laneSize) {
 
-					if (keyframeLaneHeight == 'auto') {
-						keyframeLaneHeight = options.laneHeightPx - 2;
+					if (!laneSize) {
+						return;
 					}
-
-					if (keyframeLaneHeight > options.laneHeightPx) {
-						keyframeLaneHeight = options.laneHeightPx;
+					// Draw lane 
+					if (laneSize.lane.selected && options.selectedLaneColor) {
+						ctx.fillStyle = options.selectedLaneColor;
+					} else if (laneSize.index % 2 != 0 && options.useAlternateLaneColor) {
+						ctx.fillStyle = options.alternateLaneColor || options.laneColor;
+					} else {
+						ctx.fillStyle = options.laneColor;
 					}
 
-					let margin = options.laneHeightPx - keyframeLaneHeight;
+					if (ctx.fillStyle) {
+						ctx.fillRect(laneSize.x, laneSize.y, laneSize.w, laneSize.h);
+					}
 
-					ctx.fillRect(fromPos, laneY + Math.floor(margin / 2), getDistance(fromPos, toPos), keyframeLaneHeight);
+					let keyframesSize = laneSize.keyframes;
+					if (!keyframesSize || keyframesSize.count <= 1 || !options.keyframesLaneColor) {
+						return;
+					}
 
-				}
+					ctx.fillStyle = options.keyframesLaneColor;
+					// dont draw more than required:
+					let limitedWidth = Math.min(keyframesSize.w, canvas.clientWidth);
+					ctx.fillRect(keyframesSize.x, keyframesSize.y, limitedWidth, keyframesSize.h);
+				});
 
-			});
-			ctx.restore();
+				ctx.restore();
+			}
 
 			return true;
 		}
@@ -908,6 +1003,25 @@ var animationTimeline = function (window, document) {
 			return laneY;
 		}
 
+		function getKeyframeLaneHeight(lane, laneY) {
+			let keyframeLaneHeight = lane.keyframesLaneSizePx || options.keyframesLaneSizePx;
+			if (isNaN(keyframeLaneHeight)) {
+				keyframeLaneHeight = 'auto';
+			}
+
+			if (keyframeLaneHeight == 'auto') {
+				keyframeLaneHeight = options.laneHeightPx - 2;
+			}
+
+			if (keyframeLaneHeight > options.laneHeightPx) {
+				keyframeLaneHeight = options.laneHeightPx;
+			}
+
+			let margin = options.laneHeightPx - keyframeLaneHeight;
+			y = laneY + Math.floor(margin / 2);
+			return { y: y, h: keyframeLaneHeight };
+		}
+
 		function getKeyframeSize(keyframe) {
 			if (!keyframe) {
 				return 0;
@@ -934,13 +1048,14 @@ var animationTimeline = function (window, document) {
 			}
 
 			// get center of the lane:
-			var y = getLanePosition(laneIndex) + options.laneHeightPx / 2;
+			let laneY = getLanePosition(laneIndex);
+			var y = laneY + options.laneHeightPx / 2;
 
 			let size = getKeyframeSize(keyframe);
 
 			if (size > 0) {
 				if (!isNaN(ms)) {
-					return { x: Math.floor(msToPx(ms)), y: Math.floor(y), size: size };
+					return { x: Math.floor(msToPx(ms)), y: Math.floor(y), size: size, laneY: laneY };
 				}
 			}
 
@@ -1019,7 +1134,6 @@ var animationTimeline = function (window, document) {
 
 		function drawTimeLine() {
 			ctx.save();
-
 			var thickness = options.timelineThicknessPx;
 			ctx.lineWidth = thickness * pixelRatio;
 			var timeLinePos = getSharp(Math.round(msToPx(timeLine.ms)), thickness);