Skip to content

Commit b58f982

Browse files
feat: implement node rotation functionality
1 parent 89f86c7 commit b58f982

19 files changed

Lines changed: 298 additions & 21 deletions

projects/f-flow/src/f-draggable/f-draggable.directive.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
FNodeDropToGroupFinalizeRequest,
5353
FNodeDropToGroupPreparationRequest
5454
} from './f-drop-to-group';
55+
import { FNodeRotateFinalizeRequest, FNodeRotatePreparationRequest } from './f-node-rotate';
5556

5657
@Directive({
5758
selector: "f-flow[fDraggable]",
@@ -88,6 +89,9 @@ export class FDraggableDirective extends FDraggableBase implements OnInit, After
8889
@Input()
8990
public fNodeResizeTrigger: FEventTrigger = defaultEventTrigger;
9091

92+
@Input()
93+
public fNodeRotateTrigger: FEventTrigger = defaultEventTrigger;
94+
9195
@Input()
9296
public fNodeMoveTrigger: FEventTrigger = defaultEventTrigger;
9397

@@ -184,6 +188,8 @@ export class FDraggableDirective extends FDraggableBase implements OnInit, After
184188

185189
this._fMediator.execute<void>(new FNodeResizePreparationRequest(event, this.fNodeResizeTrigger));
186190

191+
this._fMediator.execute<void>(new FNodeRotatePreparationRequest(event, this.fNodeRotateTrigger));
192+
187193
this._fMediator.execute<void>(new FNodeMovePreparationRequest(event, this.fNodeMoveTrigger));
188194

189195
this._fMediator.execute<void>(new FNodeDropToGroupPreparationRequest(event));
@@ -214,6 +220,8 @@ export class FDraggableDirective extends FDraggableBase implements OnInit, After
214220

215221
this._fMediator.execute<void>(new FNodeResizeFinalizeRequest(event));
216222

223+
this._fMediator.execute<void>(new FNodeRotateFinalizeRequest(event));
224+
217225
this._fMediator.execute<void>(new FNodeMoveFinalizeRequest(event));
218226

219227
this._fMediator.execute<void>(new FNodeDropToGroupFinalizeRequest(event));
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { IPoint, IRect, ITransformModel, PointExtensions } from '@foblex/2d';
2+
import { IFDragHandler } from '../f-drag-handler';
3+
import { FNodeBase } from '../../f-node';
4+
import { FMediator } from '@foblex/mediator';
5+
import { GetNormalizedElementRectRequest } from '../../domain';
6+
import { fInject } from '../f-injector';
7+
import { FDraggableDataContext } from '../f-draggable-data-context';
8+
import { FComponentsStore } from '../../f-storage';
9+
10+
export class FNodeRotateDragHandler implements IFDragHandler {
11+
12+
private readonly _fMediator = fInject(FMediator);
13+
private readonly _fComponentsStore = fInject(FComponentsStore);
14+
private readonly _fDraggableDataContext = fInject(FDraggableDataContext);
15+
16+
public fEventType = 'node-rotate';
17+
public fData: any;
18+
19+
private _startAngle: number = 0;
20+
21+
private _fNodeRect!: IRect;
22+
private _onDownPoint!: IPoint;
23+
24+
private get _transform(): ITransformModel {
25+
return this._fComponentsStore.fCanvas!.transform;
26+
}
27+
28+
constructor(
29+
private _fNode: FNodeBase,
30+
) {
31+
this.fData = {
32+
fNodeId: _fNode.fId,
33+
};
34+
}
35+
36+
public prepareDragSequence(): void {
37+
this._onDownPoint = this._calculateDownPoint();
38+
this._fNodeRect = this._getOriginalNodeRect();
39+
this._startAngle = this._calculateAngleBetweenVectors(this._onDownPoint) - this._fNode.rotate;
40+
}
41+
42+
private _calculateDownPoint(): IPoint {
43+
const fCanvasPosition = PointExtensions.sum(this._transform.position, this._transform.scaledPosition);
44+
return PointExtensions.sub(this._fDraggableDataContext.onPointerDownPosition, fCanvasPosition);
45+
}
46+
47+
private _calculateAngleBetweenVectors(position: IPoint): number {
48+
return Math.atan2(
49+
position.y - this._fNodeRect.gravityCenter.y,
50+
position.x - this._fNodeRect.gravityCenter.x
51+
) * (180 / Math.PI);
52+
}
53+
54+
private _getOriginalNodeRect(): IRect {
55+
return this._fMediator.execute<IRect>(new GetNormalizedElementRectRequest(this._fNode.hostElement, false));
56+
}
57+
58+
public onPointerMove(difference: IPoint): void {
59+
const position = PointExtensions.sum(this._onDownPoint, difference);
60+
const angle = this._calculateAngleBetweenVectors(position) - this._startAngle!;
61+
this._updateNodeRendering(this._normalizeAngle(angle));
62+
}
63+
64+
private _normalizeAngle(angle: number): number {
65+
return ((angle + 180) % 360) - 180;
66+
}
67+
68+
private _updateNodeRendering(angle: number): void {
69+
this._fNode.updateRotate(angle);
70+
this._fNode.redraw();
71+
}
72+
73+
public onPointerUp(): void {
74+
this._fNode.rotateChange.emit(this._fNode.rotate);
75+
}
76+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export * from './rotate-finalize';
2+
3+
export * from './rotate-preparation';
4+
5+
export * from './f-node-rotate.drag-handler';
6+
7+
export * from './providers';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { FNodeRotateFinalizeExecution } from './rotate-finalize';
2+
import { FNodeRotatePreparationExecution } from './rotate-preparation';
3+
4+
export const NODE_ROTATE_PROVIDERS = [
5+
6+
FNodeRotateFinalizeExecution,
7+
8+
FNodeRotatePreparationExecution,
9+
];
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { inject, Injectable } from '@angular/core';
2+
import { FNodeRotateFinalizeRequest } from './f-node-rotate-finalize.request';
3+
import { FExecutionRegister, IExecution } from '@foblex/mediator';
4+
import { FDraggableDataContext } from '../../f-draggable-data-context';
5+
import { FNodeRotateDragHandler } from '../f-node-rotate.drag-handler';
6+
7+
@Injectable()
8+
@FExecutionRegister(FNodeRotateFinalizeRequest)
9+
export class FNodeRotateFinalizeExecution implements IExecution<FNodeRotateFinalizeRequest, void> {
10+
11+
private readonly _fDraggableDataContext = inject(FDraggableDataContext);
12+
13+
public handle(request: FNodeRotateFinalizeRequest): void {
14+
if (!this._isValid()) {
15+
return;
16+
}
17+
this._fDraggableDataContext.draggableItems.forEach((x) => {
18+
x.onPointerUp?.();
19+
});
20+
}
21+
22+
private _isValid(): boolean {
23+
return this._fDraggableDataContext.draggableItems.some((x) =>
24+
x instanceof FNodeRotateDragHandler
25+
);
26+
}
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { IPointerEvent } from '@foblex/drag-toolkit';
2+
3+
export class FNodeRotateFinalizeRequest {
4+
5+
constructor(
6+
public event: IPointerEvent
7+
) {
8+
}
9+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './f-node-rotate-finalize.execution';
2+
3+
export * from './f-node-rotate-finalize.request';
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { inject, Injectable } from '@angular/core';
2+
import { FNodeRotatePreparationRequest } from './f-node-rotate-preparation.request';
3+
import { ITransformModel, Point } from '@foblex/2d';
4+
import { FExecutionRegister, FMediator, IExecution } from '@foblex/mediator';
5+
import { FComponentsStore } from '../../../f-storage';
6+
import { FDraggableDataContext } from '../../f-draggable-data-context';
7+
import {
8+
isValidEventTrigger,
9+
SelectAndUpdateNodeLayerRequest,
10+
} from '../../../domain';
11+
import { FNodeBase, isRotateHandle } from '../../../f-node';
12+
import { FNodeRotateDragHandler } from '../f-node-rotate.drag-handler';
13+
14+
@Injectable()
15+
@FExecutionRegister(FNodeRotatePreparationRequest)
16+
export class FNodeRotatePreparationExecution implements IExecution<FNodeRotatePreparationRequest, void> {
17+
18+
private readonly _fMediator = inject(FMediator);
19+
private readonly _fComponentsStore = inject(FComponentsStore);
20+
private readonly _fDraggableDataContext = inject(FDraggableDataContext);
21+
22+
private get _transform(): ITransformModel {
23+
return this._fComponentsStore.fCanvas!.transform;
24+
}
25+
26+
private get _fHost(): HTMLElement {
27+
return this._fComponentsStore.fFlow!.hostElement;
28+
}
29+
30+
private _fNode: FNodeBase | undefined;
31+
32+
public handle(request: FNodeRotatePreparationRequest): void {
33+
if(!this._isValid(request) || !this._isValidTrigger(request)) {
34+
return;
35+
}
36+
37+
this._selectAndUpdateNodeLayer();
38+
39+
this._fDraggableDataContext.onPointerDownScale = this._transform.scale;
40+
this._fDraggableDataContext.onPointerDownPosition = Point.fromPoint(request.event.getPosition())
41+
.elementTransform(this._fHost).div(this._transform.scale);
42+
43+
this._fDraggableDataContext.draggableItems = [
44+
new FNodeRotateDragHandler(this._fNode!)
45+
];
46+
}
47+
48+
private _isValid(request: FNodeRotatePreparationRequest): boolean {
49+
return this._fDraggableDataContext.isEmpty()
50+
&& isRotateHandle(request.event.targetElement)
51+
&& this._isNodeCanBeDragged(this._getNode(request.event.targetElement));
52+
}
53+
54+
private _isNodeCanBeDragged(fNode?: FNodeBase): boolean {
55+
return !!fNode && !fNode.fDraggingDisabled;
56+
}
57+
58+
private _getNode(element: HTMLElement): FNodeBase | undefined {
59+
this._fNode = this._fComponentsStore
60+
.fNodes.find(x => x.isContains(element));
61+
return this._fNode;
62+
}
63+
64+
private _isValidTrigger(request: FNodeRotatePreparationRequest): boolean {
65+
return isValidEventTrigger(request.event.originalEvent, request.fTrigger);
66+
}
67+
68+
private _selectAndUpdateNodeLayer() {
69+
this._fMediator.execute(
70+
new SelectAndUpdateNodeLayerRequest(this._fNode!)
71+
);
72+
}
73+
74+
// private _getHandleType(element: HTMLElement): keyof typeof EFResizeHandleType {
75+
// return getDataAttrValueFromClosestElementWithClass(element, 'fResizeHandleType', '.f-resize-handle');
76+
// }
77+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { IPointerEvent } from '@foblex/drag-toolkit';
2+
import { FEventTrigger } from '../../../domain';
3+
4+
export class FNodeRotatePreparationRequest {
5+
6+
constructor(
7+
public event: IPointerEvent,
8+
public fTrigger: FEventTrigger
9+
) {
10+
}
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './f-node-rotate-preparation.execution';
2+
3+
export * from './f-node-rotate-preparation.request';

0 commit comments

Comments
 (0)