Skip to content

Commit 81446a4

Browse files
author
Zhicheng WANG
committed
fix: 修改 movable 接口和逻辑
feat: 让 movable 支持 svg
1 parent 0663529 commit 81446a4

9 files changed

+130
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { MovableTransformDirective } from './movable-transform.directive';
2+
3+
describe('MovableTransformDirective', () => {
4+
it('should create an instance', () => {
5+
const directive = new MovableTransformDirective();
6+
expect(directive).toBeTruthy();
7+
});
8+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Directive, HostBinding } from '@angular/core';
2+
import { MovableDirective } from '@ui-model/angular/src/lib/directives/movable.directive';
3+
4+
@Directive({
5+
selector: '[uiMovableTransform]',
6+
})
7+
export class MovableTransformDirective {
8+
9+
constructor(private movable: MovableDirective) {
10+
}
11+
12+
@HostBinding('style.transform')
13+
get transform(): string {
14+
return `translate(${this.movable.offset.x}px,${this.movable.offset.y}px)`;
15+
}
16+
}

src/lib/directives/movable.directive.ts

+32-28
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
1+
import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
2+
import { PositionMapper } from '@ui-model/angular/src/lib/services/position-mapper.service';
23
import { Distance, Point } from '@ui-model/core';
34

4-
// TODO: 处理 svg 坐标系映射
5-
65
@Directive({
76
selector: '[uiMovable]',
87
exportAs: 'uiMovable',
98
})
109
export class MovableDirective {
11-
@Output('uiMoveStart') start = new EventEmitter<MouseEvent>();
12-
@Output('uiMoving') move = new EventEmitter<MouseEvent>();
13-
@Output('uiMoveStop') stop = new EventEmitter<MouseEvent>();
10+
constructor(private elementRef: ElementRef<Element>, private mapper: PositionMapper) {
11+
}
1412

15-
moving = false;
13+
@Output('uiMoveStart') start = new EventEmitter<Distance>();
14+
@Output('uiMoving') move = new EventEmitter<Distance>();
15+
@Output('uiMoveStop') stop = new EventEmitter<Distance>();
16+
offset = new Distance();
17+
private previousPoint = new Point();
1618

17-
startPos = new Point();
18-
latestPos = new Point();
19-
pos = new Point();
19+
private _moving = false;
2020

21-
get offset(): Distance {
22-
return this.pos.getDistanceTo(this.startPos);
21+
get moving(): boolean {
22+
return this._moving;
2323
}
2424

25-
get delta(): Distance {
26-
return this.pos.getDistanceTo(this.latestPos);
25+
get element(): Element {
26+
return this.elementRef.nativeElement;
2727
}
2828

2929
@HostListener('click', ['$event'])
@@ -38,25 +38,23 @@ export class MovableDirective {
3838
}
3939

4040
const target = event.target as Element;
41-
target.setPointerCapture(1);
41+
target.setPointerCapture(event.which);
4242
event.stopPropagation();
43-
this.moving = true;
44-
this.startPos = new Point(event.screenX, event.screenY);
45-
this.pos = new Point(event.screenX, event.screenY);
46-
this.start.emit(event);
43+
this._moving = true;
44+
this.previousPoint = this.svgPos(event.clientX, event.clientY);
45+
this.start.emit(this.offset);
4746
}
4847

4948
@HostListener('mouseup', ['$event'])
5049
mouseUp(event: MouseEvent): void {
5150
if (!isMajorButton(event)) {
5251
return;
5352
}
54-
(event.target as Element).releasePointerCapture(1);
53+
const target = event.target as Element;
54+
target.releasePointerCapture(event.which);
5555
event.stopPropagation();
56-
this.moving = false;
57-
this.stop.emit(event);
58-
this.startPos = new Point(event.screenX, event.screenY);
59-
this.pos = new Point(event.screenX, event.screenY);
56+
this._moving = false;
57+
this.stop.emit(this.offset);
6058
}
6159

6260
@HostListener('mousemove', ['$event'])
@@ -65,12 +63,18 @@ export class MovableDirective {
6563
return;
6664
}
6765
event.stopPropagation();
68-
if (this.moving) {
69-
this.latestPos = this.pos;
70-
this.pos = new Point(event.screenX, event.screenY);
71-
this.move.emit(event);
66+
if (this._moving) {
67+
const currentPoint = this.svgPos(event.clientX, event.clientY);
68+
this.offset.x += currentPoint.x - this.previousPoint.x;
69+
this.offset.y += currentPoint.y - this.previousPoint.y;
70+
this.previousPoint = currentPoint;
71+
this.move.emit(this.offset);
7272
}
7373
}
74+
75+
svgPos(x: number, y: number): Point {
76+
return this.mapper.mapToLocal(this.element, new Point(x, y));
77+
}
7478
}
7579

7680
function isMajorButton(event: MouseEvent): boolean {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { PositionMapper } from './position-mapper.service';
4+
5+
describe('PositionMapper', () => {
6+
beforeEach(() => TestBed.configureTestingModule({}));
7+
8+
it('should be created', () => {
9+
const service: PositionMapper = TestBed.get(PositionMapper);
10+
expect(service).toBeTruthy();
11+
});
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Injectable } from '@angular/core';
2+
import { findParent } from '@ui-model/angular/src/lib/utils/find-parent';
3+
import { screenToSvg } from '@ui-model/angular/src/lib/utils/screen-to-svg';
4+
import { svgToScreen } from '@ui-model/angular/src/lib/utils/svg-to-screen';
5+
import { Point } from '@ui-model/core/src/lib/utils/point';
6+
7+
// SVG 内部的坐标系和外部的坐标系不同,这个映射器用来在两者之间映射,必要时可以覆盖它,以实现自己的映射逻辑
8+
@Injectable({
9+
providedIn: 'root',
10+
})
11+
export class PositionMapper {
12+
mapToLocal(element: Element, pos: Point): Point {
13+
if (element instanceof SVGSVGElement) {
14+
return screenToSvg(element, pos);
15+
} else if (element instanceof SVGGraphicsElement) {
16+
return this.mapToLocal(findParent(element, SVGSVGElement), pos);
17+
}
18+
return pos;
19+
}
20+
21+
mapToScreen(element: Element, pos: Point): Point {
22+
if (element instanceof SVGSVGElement) {
23+
return svgToScreen(element, pos);
24+
} else if (element instanceof SVGGraphicsElement) {
25+
return this.mapToScreen(findParent(element, SVGSVGElement), pos);
26+
}
27+
return pos;
28+
}
29+
}
30+

src/lib/ui-model.module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { FormFieldDirective } from './directives/form-field.directive';
1414
import { FormGroupExporterDirective } from './directives/form-group-exporter.directive';
1515
import { ImgSrcFileDirective } from './directives/img-src-file.directive';
1616
import { MeasureDirective } from './directives/measure.directive';
17+
import { MovableTransformDirective } from './directives/movable-transform.directive';
1718
import { MovableDirective } from './directives/movable.directive';
1819
import { MultiBindingDirective } from './directives/multi-binding.directive';
1920
import { MultiSelectDirective } from './directives/multi-select.directive';
@@ -98,6 +99,7 @@ import { TypeNamePipe } from './pipes/type-name.pipe';
9899
DynamicComponentInputsDirective,
99100
WindowMeasureDirective,
100101
FormFieldDirective,
102+
MovableTransformDirective,
101103
],
102104
exports: [
103105
SelectDirective,
@@ -145,6 +147,7 @@ import { TypeNamePipe } from './pipes/type-name.pipe';
145147
IsInvalidDatePipe,
146148
WindowMeasureDirective,
147149
FormFieldDirective,
150+
MovableTransformDirective,
148151
],
149152
})
150153
export class UiModelModule {

src/lib/utils/find-parent.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Type } from '@angular/core';
2+
3+
export function findParent<T extends Element>(element: Element, type: Type<T>): T {
4+
if (!element) {
5+
return undefined;
6+
} else if (element instanceof SVGSVGElement) {
7+
return element as any;
8+
} else {
9+
return findParent(element.parentElement, type);
10+
}
11+
}

src/lib/utils/screen-to-svg.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Point } from '@ui-model/core';
2+
3+
export function screenToSvg(svg: SVGSVGElement, point: Point): Point {
4+
const p = svg.createSVGPoint();
5+
p.x = point.x;
6+
p.y = point.y;
7+
const innerPoint = p.matrixTransform(svg.getScreenCTM().inverse());
8+
return new Point(innerPoint.x, innerPoint.y);
9+
}

src/lib/utils/svg-to-screen.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Point } from '@ui-model/core';
2+
3+
export function svgToScreen(svg: SVGSVGElement, point: Point): Point {
4+
const p = svg.createSVGPoint();
5+
p.x = point.x;
6+
p.y = point.y;
7+
const innerPoint = p.matrixTransform(svg.getScreenCTM());
8+
return new Point(innerPoint.x, innerPoint.y);
9+
}

0 commit comments

Comments
 (0)