svgDraggableArea.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import * as React from "react";
  2. import { Vector2 } from "babylonjs/Maths/math.vector";
  3. import { KeyframeSvgPoint, IKeyframeSvgPoint } from "./keyframeSvgPoint";
  4. interface ISvgDraggableAreaProps {
  5. keyframeSvgPoints: IKeyframeSvgPoint[];
  6. updatePosition: (updatedKeyframe: IKeyframeSvgPoint, id: string) => void;
  7. scale: number;
  8. viewBoxScale: number;
  9. selectKeyframe: (id: string, multiselect: boolean) => void;
  10. selectedControlPoint: (type: string, id: string) => void;
  11. deselectKeyframes: () => void;
  12. removeSelectedKeyframes: (points: IKeyframeSvgPoint[]) => void;
  13. panningY: (panningY: number) => void;
  14. panningX: (panningX: number) => void;
  15. setCurrentFrame: (direction: number) => void;
  16. positionCanvas?: Vector2;
  17. repositionCanvas?: boolean;
  18. canvasPositionEnded: () => void;
  19. resetActionableKeyframe: () => void;
  20. }
  21. export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, { panX: number; panY: number }> {
  22. private _active: boolean;
  23. private _isCurrentPointControl: string;
  24. private _currentPointId: string;
  25. private _draggableArea: React.RefObject<SVGSVGElement>;
  26. private _panStart: Vector2;
  27. private _panStop: Vector2;
  28. private _playheadDrag: number;
  29. private _playheadSelected: boolean;
  30. private _movedX: number;
  31. private _movedY: number;
  32. readonly _dragBuffer: number;
  33. readonly _draggingMultiplier: number;
  34. constructor(props: ISvgDraggableAreaProps) {
  35. super(props);
  36. this._currentPointId = "";
  37. this._isCurrentPointControl = "";
  38. this._draggableArea = React.createRef();
  39. this._panStart = new Vector2(0, 0);
  40. this._panStop = new Vector2(0, 0);
  41. this._playheadDrag = 0;
  42. this._playheadSelected = false;
  43. this._movedX = 0;
  44. this._movedY = 0;
  45. this._dragBuffer = 4;
  46. this._draggingMultiplier = 10;
  47. this.state = { panX: 0, panY: 0 };
  48. }
  49. componentDidMount() {
  50. this._draggableArea.current?.addEventListener("keydown", this.keyDown.bind(this));
  51. this._draggableArea.current?.addEventListener("keyup", this.keyUp.bind(this));
  52. setTimeout(() => {
  53. this._draggableArea.current?.clientWidth !== undefined ? this._draggableArea.current?.clientWidth : 0;
  54. }, 500);
  55. }
  56. componentWillReceiveProps(newProps: ISvgDraggableAreaProps) {
  57. if (newProps.positionCanvas !== this.props.positionCanvas && newProps.positionCanvas !== undefined && newProps.repositionCanvas) {
  58. this.setState({ panX: newProps.positionCanvas.x, panY: newProps.positionCanvas.y }, () => {
  59. this.props.canvasPositionEnded();
  60. });
  61. }
  62. }
  63. dragStart(e: React.TouchEvent<SVGSVGElement>): void;
  64. dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
  65. dragStart(e: any): void {
  66. e.preventDefault();
  67. if (e.target.classList.contains("draggable")) {
  68. this._active = true;
  69. this._currentPointId = e.target.getAttribute("data-id");
  70. if (e.target.classList.contains("control-point")) {
  71. this._isCurrentPointControl = e.target.getAttribute("type");
  72. }
  73. }
  74. if (e.target.classList.contains("svg-playhead")) {
  75. this._active = true;
  76. this._playheadSelected = true;
  77. this._playheadDrag = e.clientX - e.currentTarget.getBoundingClientRect().left;
  78. }
  79. if (e.target.classList.contains("pannable")) {
  80. this._active = true;
  81. this._panStart.set(e.clientX - e.currentTarget.getBoundingClientRect().left, e.clientY - e.currentTarget.getBoundingClientRect().top);
  82. }
  83. }
  84. drag(e: React.TouchEvent<SVGSVGElement>): void;
  85. drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
  86. drag(e: any): void {
  87. if (this._active) {
  88. e.preventDefault();
  89. var coord = this.getMousePosition(e);
  90. if (coord !== undefined) {
  91. if (e.target.classList.contains("pannable")) {
  92. if (this._panStart.x !== 0 && this._panStart.y !== 0) {
  93. this._panStop.set(e.clientX - e.currentTarget.getBoundingClientRect().left, e.clientY - e.currentTarget.getBoundingClientRect().top);
  94. this.panDirection();
  95. }
  96. }
  97. if (e.currentTarget.classList.contains("linear") && this._playheadDrag !== 0 && this._playheadSelected) {
  98. const moving = e.clientX - e.currentTarget.getBoundingClientRect().left;
  99. const distance = moving - this._playheadDrag;
  100. const draggableAreaWidth = e.currentTarget.clientWidth;
  101. const framesInCavas = 20;
  102. const unit = draggableAreaWidth / framesInCavas;
  103. if (Math.abs(distance) >= unit / 1.25) {
  104. this.props.setCurrentFrame(Math.sign(distance));
  105. this._playheadDrag = this._playheadDrag + distance;
  106. }
  107. } else {
  108. var newPoints = [...this.props.keyframeSvgPoints];
  109. let point = newPoints.find((kf) => kf.id === this._currentPointId);
  110. if (point) {
  111. // Check for NaN values here.
  112. if (this._isCurrentPointControl === "left") {
  113. point.leftControlPoint = coord;
  114. point.isLeftActive = true;
  115. } else if (this._isCurrentPointControl === "right") {
  116. point.rightControlPoint = coord;
  117. point.isRightActive = true;
  118. } else {
  119. point.keyframePoint = coord;
  120. point.isRightActive = false;
  121. point.isLeftActive = false;
  122. }
  123. this.props.updatePosition(point, this._currentPointId);
  124. }
  125. }
  126. }
  127. }
  128. }
  129. dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
  130. dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
  131. dragEnd(e: any): void {
  132. e.preventDefault();
  133. this._active = false;
  134. this._currentPointId = "";
  135. this._isCurrentPointControl = "";
  136. this._panStart.set(0, 0);
  137. this._panStop.set(0, 0);
  138. this._playheadDrag = 0;
  139. this._playheadSelected = false;
  140. this._movedX = 0;
  141. this._movedY = 0;
  142. }
  143. getMousePosition(e: React.TouchEvent<SVGSVGElement>): Vector2 | undefined;
  144. getMousePosition(e: React.MouseEvent<SVGSVGElement, MouseEvent>): Vector2 | undefined;
  145. getMousePosition(e: any): Vector2 | undefined {
  146. if (e.touches) {
  147. e = e.touches[0];
  148. }
  149. if (this._draggableArea.current) {
  150. var svg = this._draggableArea.current as SVGSVGElement;
  151. var CTM = svg.getScreenCTM();
  152. if (CTM) {
  153. return new Vector2((e.clientX - CTM.e) / CTM.a, (e.clientY - CTM.f) / CTM.d);
  154. } else {
  155. return undefined;
  156. }
  157. } else {
  158. return undefined;
  159. }
  160. }
  161. panDirection() {
  162. let directionX = 1;
  163. if (this._movedX < this._panStop.x) {
  164. directionX = -1; //left
  165. } else {
  166. directionX = 1; //right
  167. }
  168. let directionY = 1;
  169. if (this._movedY < this._panStop.y) {
  170. directionY = -1; //top
  171. } else {
  172. directionY = 1; //bottom
  173. }
  174. console.log(this._panStop.x - this._panStart.x);
  175. const bufferX = Math.abs(this._movedX - this._panStop.x);
  176. const bufferY = Math.abs(this._movedY - this._panStop.y);
  177. let xMulti = 0;
  178. if (bufferX > this._dragBuffer) {
  179. xMulti = Math.abs(this._panStop.x - this._panStart.x) / this._draggingMultiplier;
  180. }
  181. let yMulti = 0;
  182. if (bufferY > this._dragBuffer) {
  183. yMulti = Math.abs(this._panStop.y - this._panStart.y) / this._draggingMultiplier;
  184. }
  185. this._movedX = this._panStop.x;
  186. this._movedY = this._panStop.y;
  187. let newX = this.state.panX + directionX * xMulti;
  188. let newY = this.state.panY + directionY * yMulti;
  189. this.setState({
  190. panX: Math.round(newX),
  191. panY: Math.round(newY),
  192. });
  193. this.props.panningY(Math.round(newY));
  194. this.props.panningX(Math.round(newX));
  195. }
  196. keyDown(e: KeyboardEvent) {
  197. e.preventDefault();
  198. if (e.keyCode === 17) {
  199. this._draggableArea.current?.style.setProperty("cursor", "grab");
  200. }
  201. }
  202. keyUp(e: KeyboardEvent) {
  203. e.preventDefault();
  204. if (e.keyCode === 17) {
  205. this._draggableArea.current?.style.setProperty("cursor", "initial");
  206. }
  207. if (e.keyCode === 8 || e.keyCode === 46) {
  208. const pointsToDelete = this.props.keyframeSvgPoints.filter((kf) => kf.selected);
  209. this.props.removeSelectedKeyframes(pointsToDelete);
  210. }
  211. }
  212. focus(e: React.MouseEvent<SVGSVGElement>) {
  213. e.preventDefault();
  214. this._draggableArea.current?.focus();
  215. if ((e.target as SVGSVGElement).className.baseVal == "linear pannable") {
  216. if (this.isNotControlPointActive()) {
  217. this.props.deselectKeyframes();
  218. }
  219. this.props.resetActionableKeyframe();
  220. }
  221. }
  222. isNotControlPointActive() {
  223. const activeControlPoints = this.props.keyframeSvgPoints.filter((x) => x.isLeftActive || x.isRightActive);
  224. if (activeControlPoints.length !== 0) {
  225. return false;
  226. } else {
  227. return true;
  228. }
  229. }
  230. render() {
  231. return (
  232. <>
  233. <svg
  234. style={{
  235. width: 30,
  236. height: 364,
  237. position: "absolute",
  238. zIndex: 1,
  239. pointerEvents: "none",
  240. }}
  241. >
  242. <rect x="0" y="0" width="38px" height="100%" fill="#ffffff1c"></rect>
  243. </svg>
  244. <svg
  245. className="linear pannable"
  246. ref={this._draggableArea}
  247. tabIndex={0}
  248. onMouseMove={(e) => this.drag(e)}
  249. onTouchMove={(e) => this.drag(e)}
  250. onTouchStart={(e) => this.dragStart(e)}
  251. onTouchEnd={(e) => this.dragEnd(e)}
  252. onMouseDown={(e) => this.dragStart(e)}
  253. onMouseUp={(e) => this.dragEnd(e)}
  254. onMouseLeave={(e) => this.dragEnd(e)}
  255. onClick={(e) => this.focus(e)}
  256. viewBox={`${this.state.panX} ${this.state.panY} ${Math.round(this.props.scale * 200)} ${Math.round(this.props.scale * 100)}`}
  257. >
  258. {this.props.children}
  259. {this.props.keyframeSvgPoints.map((keyframe, i) => (
  260. <KeyframeSvgPoint
  261. key={`${keyframe.id}_${i}`}
  262. id={keyframe.id}
  263. keyframePoint={keyframe.keyframePoint}
  264. leftControlPoint={keyframe.leftControlPoint}
  265. rightControlPoint={keyframe.rightControlPoint}
  266. isLeftActive={keyframe.isLeftActive}
  267. isRightActive={keyframe.isRightActive}
  268. selected={keyframe.selected}
  269. selectedControlPoint={(type: string, id: string) => this.props.selectedControlPoint(type, id)}
  270. selectKeyframe={(id: string, multiselect: boolean) => this.props.selectKeyframe(id, multiselect)}
  271. />
  272. ))}
  273. </svg>
  274. </>
  275. );
  276. }
  277. }