Skip to content

Commit 59cadfa

Browse files
committed
feat(): Add distance-guide to extensions
1 parent b294849 commit 59cadfa

File tree

18 files changed

+848
-0
lines changed

18 files changed

+848
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [next]
44

5+
- feat(): Add distance-guide to extensions [#10458](https://github.com/fabricjs/fabric.js/discussions/10458)
56
- fix(): BREAKING Fix text positioning [#10803](https://github.com/fabricjs/fabric.js/pull/10803)
67
- fix(AligningGuidelines): Guidelines features updates [#10120] (https://github.com/fabricjs/fabric.js/pull/10120)
78
- chore(deps-dev): bump inquirer from 12.9.6 to 12.10.0 [#10789](https://github.com/fabricjs/fabric.js/pull/10789)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { expect, test } from '../../fixtures/base';
2+
import { ObjectUtil } from '../../utils/ObjectUtil';
3+
import type { Rect } from 'fabric';
4+
5+
test('Distance guide', async ({ page, canvasUtil }) => {
6+
const rectUtil = new ObjectUtil<Rect>(page, 'rect3');
7+
8+
await test.step('press Alt to show distance', async () => {
9+
const rect2Pos = await new ObjectUtil<Rect>(
10+
page,
11+
'rect2',
12+
).getObjectCenter();
13+
// select the rect2
14+
await page.mouse.move(rect2Pos.x, rect2Pos.y);
15+
await page.mouse.down();
16+
await page.mouse.up();
17+
18+
const rectPos = await rectUtil.getObjectCenter();
19+
await page.mouse.move(rectPos.x, rectPos.y);
20+
page.keyboard.down('Alt');
21+
await canvasUtil.renderAll();
22+
expect(await canvasUtil.screenshot()).toMatchSnapshot({
23+
name: 'distance.png',
24+
});
25+
});
26+
27+
await test.step('drag the rect3', async () => {
28+
const rectPos = await rectUtil.getObjectCenter();
29+
await page.mouse.move(rectPos.x, rectPos.y);
30+
await page.mouse.down();
31+
// 410, make +3
32+
await page.mouse.move(rectPos.x + 413, rectPos.y + 40, {
33+
steps: 40,
34+
});
35+
await canvasUtil.renderAll();
36+
expect(await canvasUtil.screenshot()).toMatchSnapshot({
37+
name: 'to-the-right.png',
38+
});
39+
// 215, make -2
40+
await page.mouse.move(rectPos.x + 213, rectPos.y + 40, {
41+
steps: 40,
42+
});
43+
await canvasUtil.renderAll();
44+
expect(await canvasUtil.screenshot()).toMatchSnapshot({
45+
name: 'to-the-center.png',
46+
});
47+
// 20, make 0
48+
await page.mouse.move(rectPos.x + 20, rectPos.y + 40, {
49+
steps: 40,
50+
});
51+
expect(await canvasUtil.screenshot()).toMatchSnapshot({
52+
name: 'to-the-left.png',
53+
});
54+
});
55+
});
2.05 KB
Loading
1.49 KB
Loading
1.33 KB
Loading
1.19 KB
Loading

e2e/tests/distance-guide/index.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Runs in the **BROWSER**
3+
* Imports are defined in 'e2e/imports.ts'
4+
*/
5+
6+
import { Rect } from 'fabric';
7+
import { DistanceGuide } from 'fabric/extensions';
8+
import { beforeAll } from '../test';
9+
10+
beforeAll(async (canvas) => {
11+
canvas.setDimensions({ width: 450, height: 100 });
12+
const rect1 = new Rect({
13+
strokeWidth: 0,
14+
left: 150,
15+
top: 30,
16+
width: 50,
17+
height: 50,
18+
fill: 'green',
19+
});
20+
21+
const rect2 = new Rect({
22+
strokeWidth: 0,
23+
left: 280,
24+
top: 30,
25+
width: 50,
26+
height: 50,
27+
fill: 'yellow',
28+
});
29+
30+
const rect3 = new Rect({
31+
strokeWidth: 0,
32+
left: 0,
33+
top: 0,
34+
width: 50,
35+
height: 50,
36+
fill: 'black',
37+
});
38+
39+
new DistanceGuide(canvas);
40+
41+
canvas.add(rect1, rect2, rect3);
42+
43+
return { rect1, rect2, rect3 };
44+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Distance Guide
2+
3+
When the coordinates of shape A are (0, 0) and the coordinates of shape B are (a, 0), dragging a shape to (2a, 0) will display a distance guide line.
4+
When the coordinates of shape A are (0, 0) and the coordinates of shape B are (2a, 0), dragging a shape to (a, 0) will display a distance guide line.
5+
When the coordinates of shape A are (0, 0) and the coordinates of shape B are (a, 0), dragging a shape to (-a, 0) will display a distance guide line.
6+
When shape A is selected, moving the mouse over shape B while holding the Alt key will display a distance guide line between A and B.
7+
8+
## How to use it
9+
10+
```ts
11+
import { DistanceGuide } from 'fabric/extensions';
12+
13+
const config = {
14+
/** The color of the guide line */
15+
color = 'rgba(255,0,0,0.9)';
16+
/** The fill color of the text */
17+
fillStyle = 'rgba(255,255,255,1)';
18+
/** The width of the guide line */
19+
lineWidth = 1;
20+
/** The style of the dashed line */
21+
lineDash = [6, 2];
22+
/** The offset of the dashed line */
23+
lineDashOffset = 3;
24+
/** The size of the text */
25+
fontSize = 11;
26+
/** The fontFamily of the text */
27+
fontFamily = 'sans-serif';
28+
/** The spacing of the text within the rectangle */
29+
padding = 4;
30+
/** The distance between the text and the guide line */
31+
space = 5;
32+
/** The detection distance when moving the graphic */
33+
margin = 8;
34+
};
35+
36+
const distanceGuide = new DistanceGuide(myCanvas, options);
37+
38+
// in order to disable distance Guide later:
39+
40+
distanceGuide.dispose();
41+
```

extensions/distance-guide/index.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import type {
2+
BasicTransformEvent,
3+
Canvas,
4+
FabricObject,
5+
TPointerEventInfo,
6+
} from 'fabric';
7+
import { getFabricWindow } from 'fabric';
8+
import type {
9+
AltMap,
10+
DistanceGuideProps,
11+
DrawLineMapProps,
12+
MovingMap,
13+
} from './typedefs';
14+
import type { DrawTextProps } from './util';
15+
import {
16+
drawAltMap,
17+
drawLineMap,
18+
drawMovingMap,
19+
drawText,
20+
moving,
21+
setAltMap,
22+
} from './util';
23+
24+
export type { DistanceGuideProps };
25+
export class DistanceGuide {
26+
canvas: Canvas;
27+
/** The color of the guide line */
28+
color = 'rgba(255,0,0,0.9)';
29+
/** The fill color of the text */
30+
fillStyle = 'rgba(255,255,255,1)';
31+
/** The width of the guide line */
32+
lineWidth = 1;
33+
/** The style of the dashed line */
34+
lineDash = [6, 2];
35+
/** The offset of the dashed line */
36+
lineDashOffset = 3;
37+
/** The size of the text */
38+
fontSize = 11;
39+
/** The fontFamily of the text */
40+
fontFamily = 'sans-serif';
41+
/** The spacing of the text within the rectangle */
42+
padding = 4;
43+
/** The distance between the text and the guide line */
44+
space = 5;
45+
/** The detection distance when moving the graphic */
46+
margin = 8;
47+
48+
altMap?: AltMap;
49+
movingMap?: MovingMap;
50+
51+
private target?: FabricObject;
52+
53+
constructor(canvas: Canvas, options: Partial<DistanceGuideProps> = {}) {
54+
this.canvas = canvas;
55+
Object.assign(this, options);
56+
this.mouseOver = this.mouseOver.bind(this);
57+
this.mouseOut = this.mouseOut.bind(this);
58+
this.mouseDown = this.mouseDown.bind(this);
59+
this.moving = this.moving.bind(this);
60+
this.beforeRender = this.beforeRender.bind(this);
61+
this.afterRender = this.afterRender.bind(this);
62+
this.keydown = this.keydown.bind(this);
63+
this.keyup = this.keyup.bind(this);
64+
this.initBehavior();
65+
}
66+
initBehavior() {
67+
this.canvas.on('mouse:over', this.mouseOver);
68+
this.canvas.on('mouse:out', this.mouseOut);
69+
this.canvas.on('mouse:down', this.mouseDown);
70+
this.canvas.on('object:moving', this.moving);
71+
this.canvas.on('before:render', this.beforeRender);
72+
this.canvas.on('after:render', this.afterRender);
73+
const win = getFabricWindow();
74+
win.addEventListener('keydown', this.keydown);
75+
win.addEventListener('keyup', this.keyup);
76+
}
77+
mouseDown() {
78+
delete this.altMap;
79+
this.canvas.once('mouse:up', () => {
80+
delete this.movingMap;
81+
});
82+
}
83+
moving(e: BasicTransformEvent & { target: FabricObject }) {
84+
moving.call(this, e);
85+
}
86+
beforeRender() {
87+
this.canvas.clearContext(this.canvas.contextTop);
88+
}
89+
afterRender() {
90+
drawAltMap.call(this);
91+
drawMovingMap.call(this);
92+
}
93+
mouseOver(e: TPointerEventInfo) {
94+
const target = e.target;
95+
this.target = target?.selectable ? target : undefined;
96+
if (!e.e.altKey) return;
97+
this.setAltMap();
98+
this.canvas.requestRenderAll();
99+
}
100+
mouseOut(e: TPointerEventInfo) {
101+
this.target = undefined;
102+
if (!e.e.altKey) return;
103+
delete this.altMap;
104+
this.canvas.requestRenderAll();
105+
}
106+
keydown(e: KeyboardEvent) {
107+
if (e.key != 'Alt') return;
108+
this.setAltMap();
109+
this.canvas.requestRenderAll();
110+
}
111+
keyup(e: KeyboardEvent) {
112+
if (e.key != 'Alt') return;
113+
delete this.altMap;
114+
this.canvas.requestRenderAll();
115+
}
116+
setAltMap(origin = this.canvas._activeObject, target = this.target) {
117+
setAltMap.call(this, origin, target);
118+
}
119+
drawLineMap(props: DrawLineMapProps) {
120+
drawLineMap.call(this, props);
121+
}
122+
drawText(props: DrawTextProps) {
123+
drawText.call(this, props);
124+
}
125+
dispose() {
126+
this.canvas.off('mouse:over', this.mouseOver);
127+
this.canvas.off('mouse:out', this.mouseOut);
128+
this.canvas.off('mouse:down', this.mouseDown);
129+
this.canvas.off('object:moving', this.moving);
130+
this.canvas.off('before:render', this.beforeRender);
131+
this.canvas.off('after:render', this.afterRender);
132+
const win = getFabricWindow();
133+
win.removeEventListener('keydown', this.keydown);
134+
win.removeEventListener('keyup', this.keyup);
135+
}
136+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { Point, TBBox } from 'fabric';
2+
3+
export type AltMapPoint = {
4+
target: Point;
5+
xPoint: Point;
6+
yPoint: Point;
7+
};
8+
9+
export type AltMap = {
10+
origin: TBBox;
11+
target: TBBox;
12+
points: AltMapPoint[];
13+
};
14+
15+
export type DrawLineMapProps = AltMapPoint & {
16+
ctx: CanvasRenderingContext2D;
17+
};
18+
19+
export type MovingMapLine = {
20+
origin: Point;
21+
target: Point;
22+
};
23+
export type MovingMap = {
24+
xLines: MovingMapLine[];
25+
yLines: MovingMapLine[];
26+
};
27+
28+
export type DistanceGuideProps = {
29+
/** The color of the guide line */
30+
color: string;
31+
/** The fill color of the text */
32+
fillStyle: string;
33+
/** The width of the guide line */
34+
lineWidth: number;
35+
/** The style of the dashed line */
36+
lineDash: number[];
37+
/** The offset of the dashed line */
38+
lineDashOffset: number;
39+
/** The size of the text */
40+
fontSize: number;
41+
/** The fontFamily of the text */
42+
fontFamily: string;
43+
/** The spacing of the text within the rectangle */
44+
padding: number;
45+
/** The distance between the text and the guide line */
46+
space: number;
47+
/** The detection distance when moving the graphic */
48+
margin: number;
49+
};

0 commit comments

Comments
 (0)