123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- import * as React from "react";
- import { Vector2 } from "babylonjs/Maths/math.vector";
- import { IKeyframeSvgPoint } from "./keyframeSvgPoint";
- interface ISvgDraggableAreaProps {
- keyframeSvgPoints: IKeyframeSvgPoint[];
- updatePosition: (updatedKeyframe: IKeyframeSvgPoint, id: string) => void;
- scale: number;
- viewBoxScale: number;
- deselectKeyframes: () => void;
- removeSelectedKeyframes: (points: IKeyframeSvgPoint[]) => void;
- panningY: (panningY: number) => void;
- panningX: (panningX: number) => void;
- setCurrentFrame: (direction: number) => void;
- positionCanvas?: Vector2;
- repositionCanvas?: boolean;
- canvasPositionEnded: () => void;
- resetActionableKeyframe: () => void;
- framesInCanvasView: { from: number; to: number };
- framesResized: number;
- }
- /**
- * The SvgDraggableArea is a wrapper for SVG Canvas the interaction
- *
- * Here we control the drag and key behavior for the SVG components.
- */
- export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, { panX: number; panY: number }> {
- private _active: boolean;
- private _isCurrentPointControl: string;
- private _currentPointId: string;
- private _draggableArea: React.RefObject<SVGSVGElement>;
- private _panStart: Vector2;
- private _panStop: Vector2;
- private _playheadDrag: number;
- private _playheadSelected: boolean;
- private _movedX: number;
- private _movedY: number;
- private _isControlKeyPress: boolean;
- readonly _dragBuffer: number;
- readonly _draggingMultiplier: number;
- constructor(props: ISvgDraggableAreaProps) {
- super(props);
- this._currentPointId = "";
- this._isCurrentPointControl = "";
- this._draggableArea = React.createRef();
- this._panStart = new Vector2(0, 0);
- this._panStop = new Vector2(0, 0);
- this._playheadDrag = 0;
- this._playheadSelected = false;
- this._movedX = 0;
- this._movedY = 0;
- this._dragBuffer = 3;
- this._draggingMultiplier = 10;
- this._isControlKeyPress = false;
- this.state = { panX: 0, panY: 0 };
- }
- componentDidMount() {
- this._draggableArea.current?.addEventListener("keydown", this.keyDown.bind(this));
- this._draggableArea.current?.addEventListener("keyup", this.keyUp.bind(this));
- setTimeout(() => {
- this._draggableArea.current?.clientWidth !== undefined ? this._draggableArea.current?.clientWidth : 0;
- }, 500);
- }
- componentDidUpdate(prevProps: ISvgDraggableAreaProps) {
- if (
- this.props.positionCanvas !== prevProps.positionCanvas &&
- this.props.positionCanvas !== undefined &&
- this.props.repositionCanvas
- ) {
- this.setState(
- {
- panX: this.props.positionCanvas.x,
- panY: this.props.positionCanvas.y,
- },
- () => {
- this.props.canvasPositionEnded();
- }
- );
- }
- }
- dragStart = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
- e.preventDefault();
- if ((e.target as SVGSVGElement).classList.contains("draggable")) {
- this._active = true;
- const dataId = (e.target as SVGSVGElement).getAttribute("data-id");
- if (dataId !== null) {
- this._currentPointId = dataId;
- }
- if ((e.target as SVGSVGElement).classList.contains("control-point")) {
- const type = (e.target as SVGSVGElement).getAttribute("type");
- if (type !== null) {
- this._isCurrentPointControl = type;
- }
- }
- }
- if ((e.target as SVGSVGElement).classList.contains("svg-playhead")) {
- this._active = true;
- this._playheadSelected = true;
- this._playheadDrag = e.clientX - e.currentTarget.getBoundingClientRect().left;
- }
- if ((e.target as SVGSVGElement).classList.contains("pannable")) {
- if (this._isControlKeyPress) {
- this._active = true;
- this._panStart.set(
- e.clientX - e.currentTarget.getBoundingClientRect().left,
- e.clientY - e.currentTarget.getBoundingClientRect().top
- );
- }
- }
- };
- drag = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
- if (this._active) {
- e.preventDefault();
- var coord = this.getMousePosition(e);
- if (coord !== undefined) {
- // Handles the canvas panning
- if ((e.target as SVGSVGElement).classList.contains("pannable")) {
- if (this._isControlKeyPress) {
- if (this._panStart.x !== 0 && this._panStart.y !== 0) {
- this._panStop.set(
- e.clientX - e.currentTarget.getBoundingClientRect().left,
- e.clientY - e.currentTarget.getBoundingClientRect().top
- );
- this.panDirection();
- }
- }
- }
- // Handles the playhead dragging
- if (
- e.currentTarget.classList.contains("linear") &&
- this._playheadDrag !== 0 &&
- this._playheadSelected
- ) {
- const moving = e.clientX - e.currentTarget.getBoundingClientRect().left;
- const draggableAreaWidth = e.currentTarget.clientWidth;
- const initialFrame = this.props.framesInCanvasView.from;
- const lastFrame = this.props.framesInCanvasView.to;
- const framesInCanvas = lastFrame - initialFrame;
- const unit = draggableAreaWidth / framesInCanvas;
- const newFrame = Math.round(moving / unit) + initialFrame;
- this.props.setCurrentFrame(newFrame);
- } else {
- // Handles the control point dragging
- var newPoints = [...this.props.keyframeSvgPoints];
- let point = newPoints.find((kf) => kf.id === this._currentPointId);
- if (point) {
- if (this._isCurrentPointControl === "left") {
- point.leftControlPoint = coord;
- point.isLeftActive = true;
- point.isRightActive = false;
- } else if (this._isCurrentPointControl === "right") {
- point.rightControlPoint = coord;
- point.isRightActive = true;
- point.isLeftActive = false;
- } else {
- point.keyframePoint = coord;
- point.isRightActive = false;
- point.isLeftActive = false;
- }
- this.props.updatePosition(point, this._currentPointId);
- }
- }
- }
- }
- };
- dragEnd = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
- e.preventDefault();
- this._active = false;
- this._currentPointId = "";
- this._isCurrentPointControl = "";
- this._panStart.set(0, 0);
- this._panStop.set(0, 0);
- this._playheadDrag = 0;
- this._playheadSelected = false;
- this._movedX = 0;
- this._movedY = 0;
- };
- getMousePosition = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): Vector2 | undefined => {
- if (this._draggableArea.current) {
- var svg = this._draggableArea.current as SVGSVGElement;
- var CTM = svg.getScreenCTM();
- if (CTM) {
- return new Vector2((e.clientX - CTM.e) / CTM.a, (e.clientY - CTM.f) / CTM.d);
- } else {
- return undefined;
- }
- } else {
- return undefined;
- }
- };
- /**
- * Handles the canvas panning direction and sets the X and Y values to move the
- * SVG canvas
- */
- panDirection() {
- let directionX = 1;
- if (this._movedX < this._panStop.x) {
- directionX = -1; //left
- } else {
- directionX = 1; //right
- }
- let directionY = 1;
- if (this._movedY < this._panStop.y) {
- directionY = -1; //top
- } else {
- directionY = 1; //bottom
- }
- const bufferX = this._movedX === 0 ? 1 : Math.abs(this._movedX - this._panStop.x);
- const bufferY = this._movedY === 0 ? 1 : Math.abs(this._movedY - this._panStop.y);
- let xMulti = 0;
- if (bufferX >= this._dragBuffer) {
- xMulti = Math.round(Math.abs(bufferX - this._dragBuffer) / 2.5);
- }
- let yMulti = 0;
- if (bufferY >= this._dragBuffer) {
- yMulti = Math.round(Math.abs(bufferY - this._dragBuffer) / 2.5);
- }
- this._movedX = this._panStop.x;
- this._movedY = this._panStop.y;
- let newX = this.state.panX + directionX * xMulti;
- let newY = this.state.panY + directionY * yMulti;
- this.setState({
- panX: Math.round(newX),
- panY: Math.round(newY),
- });
- this.props.panningY(Math.round(newY));
- this.props.panningX(Math.round(newX));
- }
- keyDown(e: KeyboardEvent) {
- e.preventDefault();
- if (e.keyCode === 17 || e.keyCode === 32) {
- this._draggableArea.current?.style.setProperty("cursor", "grab");
- this._isControlKeyPress = true;
- }
- }
- keyUp(e: KeyboardEvent) {
- e.preventDefault();
- if (e.keyCode === 17 || e.keyCode === 32) {
- this._draggableArea.current?.style.setProperty("cursor", "initial");
- this._isControlKeyPress = false;
- this._active = false;
- this._panStart.set(0, 0);
- this._panStop.set(0, 0);
- this._movedX = 0;
- this._movedY = 0;
- }
- if (e.keyCode === 8 || e.keyCode === 46) {
- const pointsToDelete = this.props.keyframeSvgPoints.filter((kf) => kf.selected);
- this.props.removeSelectedKeyframes(pointsToDelete);
- }
- }
- focus = (e: React.MouseEvent<SVGSVGElement>) => {
- e.preventDefault();
- this._draggableArea.current?.focus();
- if ((e.target as SVGSVGElement).className.baseVal == "linear pannable") {
- if (this.isNotControlPointActive()) {
- this.props.deselectKeyframes();
- }
- this.props.resetActionableKeyframe();
- }
- };
- isNotControlPointActive() {
- const activeControlPoints = this.props.keyframeSvgPoints.filter((x) => x.isLeftActive || x.isRightActive);
- if (activeControlPoints.length !== 0) {
- return false;
- } else {
- return true;
- }
- }
- render() {
- const viewBoxScaling = `${this.props.positionCanvas?.x} ${this.props.positionCanvas?.y} ${Math.round(
- this.props.scale * 200
- )} ${Math.round(this.props.scale * 100)}`;
- return (
- <>
- <svg
- className="linear pannable"
- ref={this._draggableArea}
- tabIndex={0}
- onMouseMove={this.drag}
- onMouseDown={this.dragStart}
- onMouseUp={this.dragEnd}
- onMouseLeave={this.dragEnd}
- onClick={this.focus}
- viewBox={viewBoxScaling}
- >
- {this.props.children}
- </svg>
- </>
- );
- }
- }
|