123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- import * as React from 'react';
- import { Vector2 } from 'babylonjs/Maths/math.vector';
- import { KeyframeSvgPoint, IKeyframeSvgPoint } from './keyframeSvgPoint';
- interface ISvgDraggableAreaProps {
- keyframeSvgPoints: IKeyframeSvgPoint[];
- updatePosition: (updatedKeyframe: IKeyframeSvgPoint, id: string) => void;
- scale: number;
- viewBoxScale: number;
- selectKeyframe: (id: string, multiselect: boolean) => void;
- selectedControlPoint: (type: string, id: string) => void;
- deselectKeyframes: () => void;
- removeSelectedKeyframes: (points: IKeyframeSvgPoint[]) => void;
- panningY: (panningY: number) => void;
- panningX: (panningX: number) => void;
- setCurrentFrame: (direction: number) => void;
- positionCanvas?: number;
- repositionCanvas?: boolean;
- canvasPositionEnded: () => void;
- resetActionableKeyframe: () => void;
- }
- 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;
- 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.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);
- }
- componentWillReceiveProps(newProps: ISvgDraggableAreaProps) {
- if (
- newProps.positionCanvas !== this.props.positionCanvas &&
- newProps.positionCanvas !== undefined &&
- newProps.repositionCanvas
- ) {
- this.setState({ panX: newProps.positionCanvas }, () => {
- this.props.canvasPositionEnded();
- });
- }
- }
- dragStart(e: React.TouchEvent<SVGSVGElement>): void;
- dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
- dragStart(e: any): void {
- e.preventDefault();
- if (e.target.classList.contains('draggable')) {
- this._active = true;
- this._currentPointId = e.target.getAttribute('data-id');
- if (e.target.classList.contains('control-point')) {
- this._isCurrentPointControl = e.target.getAttribute('type');
- }
- }
- if (e.target.classList.contains('svg-playhead')) {
- this._active = true;
- this._playheadSelected = true;
- this._playheadDrag =
- e.clientX - e.currentTarget.getBoundingClientRect().left;
- }
- if (e.target.classList.contains('pannable')) {
- this._active = true;
- this._panStart.set(e.clientX, e.clientY);
- }
- }
- drag(e: React.TouchEvent<SVGSVGElement>): void;
- drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
- drag(e: any): void {
- if (this._active) {
- e.preventDefault();
- var coord = this.getMousePosition(e);
- if (coord !== undefined) {
- if (e.target.classList.contains('pannable')) {
- if (this._panStart.x !== 0 && this._panStart.y !== 0) {
- this._panStop.set(e.clientX, e.clientY);
- this.panDirection();
- }
- }
- if (
- e.currentTarget.classList.contains('linear') &&
- this._playheadDrag !== 0 &&
- this._playheadSelected
- ) {
- const moving =
- e.clientX - e.currentTarget.getBoundingClientRect().left;
- const distance = moving - this._playheadDrag;
- const draggableAreaWidth = e.currentTarget.clientWidth;
- const framesInCavas = 20;
- const unit = draggableAreaWidth / framesInCavas;
- if (Math.abs(distance) >= unit / 1.25) {
- this.props.setCurrentFrame(Math.sign(distance));
- this._playheadDrag = this._playheadDrag + distance;
- }
- } else {
- var newPoints = [...this.props.keyframeSvgPoints];
- let point = newPoints.find((kf) => kf.id === this._currentPointId);
- if (point) {
- // Check for NaN values here.
- if (this._isCurrentPointControl === 'left') {
- point.leftControlPoint = coord;
- point.isLeftActive = true;
- } else if (this._isCurrentPointControl === 'right') {
- point.rightControlPoint = coord;
- point.isRightActive = true;
- } else {
- point.keyframePoint = coord;
- point.isRightActive = false;
- point.isLeftActive = false;
- }
- this.props.updatePosition(point, this._currentPointId);
- }
- }
- }
- }
- }
- dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
- dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
- dragEnd(e: any): 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;
- }
- getMousePosition(e: React.TouchEvent<SVGSVGElement>): Vector2 | undefined;
- getMousePosition(
- e: React.MouseEvent<SVGSVGElement, MouseEvent>
- ): Vector2 | undefined;
- getMousePosition(e: any): Vector2 | undefined {
- if (e.touches) {
- e = e.touches[0];
- }
- 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;
- }
- }
- panDirection() {
- let movementX = this._panStart.x - this._panStop.x;
- let movementY = this._panStart.y - this._panStop.y;
- let newX = this.state.panX + movementX / 20;
- let newY = this.state.panY + movementY / 20;
- this.setState({
- panX: Math.round(newX),
- panY: Math.round(newY),
- });
- this.props.panningY(Math.round(newY));
- this.props.panningX(Math.round(newX));
- }
- panTo(direction: string, value: number) {
- switch (direction) {
- case 'left':
- (this._draggableArea.current
- ?.parentElement as HTMLDivElement).scrollLeft -= value * 1;
- break;
- case 'right':
- (this._draggableArea.current
- ?.parentElement as HTMLDivElement).scrollLeft += value * 1;
- break;
- case 'top':
- break;
- case 'down':
- break;
- }
- }
- keyDown(e: KeyboardEvent) {
- e.preventDefault();
- if (e.keyCode === 17) {
- this._draggableArea.current?.style.setProperty('cursor', 'grab');
- }
- }
- keyUp(e: KeyboardEvent) {
- e.preventDefault();
- if (e.keyCode === 17) {
- this._draggableArea.current?.style.setProperty('cursor', 'initial');
- }
- if (e.keyCode === 8) {
- 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() {
- return (
- <>
- <svg
- style={{
- width: 30,
- height: 364,
- position: 'absolute',
- zIndex: 1,
- pointerEvents: 'none',
- }}
- >
- <rect x='0' y='0' width='30px' height='100%' fill='#ffffff1c'></rect>
- </svg>
- <svg
- className='linear pannable'
- ref={this._draggableArea}
- tabIndex={0}
- onMouseMove={(e) => this.drag(e)}
- onTouchMove={(e) => this.drag(e)}
- onTouchStart={(e) => this.dragStart(e)}
- onTouchEnd={(e) => this.dragEnd(e)}
- onMouseDown={(e) => this.dragStart(e)}
- onMouseUp={(e) => this.dragEnd(e)}
- onMouseLeave={(e) => this.dragEnd(e)}
- onClick={(e) => this.focus(e)}
- viewBox={`${this.state.panX} ${this.state.panY} ${Math.round(
- this.props.scale * 200
- )} ${Math.round(this.props.scale * 100)}`}
- >
- {this.props.children}
- {this.props.keyframeSvgPoints.map((keyframe, i) => (
- <KeyframeSvgPoint
- key={`${keyframe.id}_${i}`}
- id={keyframe.id}
- keyframePoint={keyframe.keyframePoint}
- leftControlPoint={keyframe.leftControlPoint}
- rightControlPoint={keyframe.rightControlPoint}
- isLeftActive={keyframe.isLeftActive}
- isRightActive={keyframe.isRightActive}
- selected={keyframe.selected}
- selectedControlPoint={(type: string, id: string) =>
- this.props.selectedControlPoint(type, id)
- }
- selectKeyframe={(id: string, multiselect: boolean) =>
- this.props.selectKeyframe(id, multiselect)
- }
- />
- ))}
- </svg>
- </>
- );
- }
- }
|