mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 13:29:02 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import type { PointTestOptions } from '@blocksuite/block-std/gfx';
|
||||
import type { IBound, IVec } from '@blocksuite/global/utils';
|
||||
import {
|
||||
Bound,
|
||||
getCenterAreaBounds,
|
||||
getPointsFromBoundWithRotation,
|
||||
linePolygonIntersects,
|
||||
pointInPolygon,
|
||||
PointLocation,
|
||||
pointOnPolygonStoke,
|
||||
polygonGetPointTangent,
|
||||
polygonNearestPoint,
|
||||
rotatePoints,
|
||||
} from '@blocksuite/global/utils';
|
||||
|
||||
import { DEFAULT_CENTRAL_AREA_RATIO } from '../../../consts/index.js';
|
||||
import type { ShapeElementModel } from '../shape.js';
|
||||
|
||||
export const diamond = {
|
||||
points({ x, y, w, h }: IBound): IVec[] {
|
||||
return [
|
||||
[x, y + h / 2],
|
||||
[x + w / 2, y],
|
||||
[x + w, y + h / 2],
|
||||
[x + w / 2, y + h],
|
||||
];
|
||||
},
|
||||
draw(ctx: CanvasRenderingContext2D, { x, y, w, h, rotate = 0 }: IBound) {
|
||||
const cx = x + w / 2;
|
||||
const cy = y + h / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
ctx.translate(-cx, -cy);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + h / 2);
|
||||
ctx.lineTo(x + w / 2, y);
|
||||
ctx.lineTo(x + w, y + h / 2);
|
||||
ctx.lineTo(x + w / 2, y + h);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
includesPoint(
|
||||
this: ShapeElementModel,
|
||||
x: number,
|
||||
y: number,
|
||||
options: PointTestOptions
|
||||
) {
|
||||
const point: IVec = [x, y];
|
||||
const points = getPointsFromBoundWithRotation(this, diamond.points);
|
||||
|
||||
let hit = pointOnPolygonStoke(
|
||||
point,
|
||||
points,
|
||||
(options?.hitThreshold ?? 1) / (options.zoom ?? 1)
|
||||
);
|
||||
|
||||
if (!hit) {
|
||||
if (!options.ignoreTransparent || this.filled) {
|
||||
hit = pointInPolygon([x, y], points);
|
||||
} else {
|
||||
// If shape is not filled or transparent
|
||||
const text = this.text;
|
||||
if (!text || !text.length) {
|
||||
// Check the center area of the shape
|
||||
const centralBounds = getCenterAreaBounds(
|
||||
this,
|
||||
DEFAULT_CENTRAL_AREA_RATIO
|
||||
);
|
||||
const centralPoints = getPointsFromBoundWithRotation(
|
||||
centralBounds,
|
||||
diamond.points
|
||||
);
|
||||
hit = pointInPolygon(point, centralPoints);
|
||||
} else if (this.textBound) {
|
||||
hit = pointInPolygon(
|
||||
point,
|
||||
getPointsFromBoundWithRotation(
|
||||
this,
|
||||
() => Bound.from(this.textBound!).points
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
},
|
||||
|
||||
containsBound(bounds: Bound, element: ShapeElementModel) {
|
||||
const points = getPointsFromBoundWithRotation(element, diamond.points);
|
||||
return points.some(point => bounds.containsPoint(point));
|
||||
},
|
||||
|
||||
getNearestPoint(point: IVec, element: ShapeElementModel) {
|
||||
const points = getPointsFromBoundWithRotation(element, diamond.points);
|
||||
return polygonNearestPoint(points, point);
|
||||
},
|
||||
|
||||
getLineIntersections(start: IVec, end: IVec, element: ShapeElementModel) {
|
||||
const points = getPointsFromBoundWithRotation(element, diamond.points);
|
||||
return linePolygonIntersects(start, end, points);
|
||||
},
|
||||
|
||||
getRelativePointLocation(position: IVec, element: ShapeElementModel) {
|
||||
const bound = Bound.deserialize(element.xywh);
|
||||
const point = bound.getRelativePoint(position);
|
||||
let points = diamond.points(bound);
|
||||
points.push(point);
|
||||
|
||||
points = rotatePoints(points, bound.center, element.rotate);
|
||||
const rotatePoint = points.pop() as IVec;
|
||||
const tangent = polygonGetPointTangent(points, rotatePoint);
|
||||
return new PointLocation(rotatePoint, tangent);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,202 @@
|
||||
import type { PointTestOptions } from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
Bound,
|
||||
clamp,
|
||||
getPointsFromBoundWithRotation,
|
||||
type IBound,
|
||||
type IVec,
|
||||
lineEllipseIntersects,
|
||||
pointInEllipse,
|
||||
pointInPolygon,
|
||||
PointLocation,
|
||||
rotatePoints,
|
||||
toRadian,
|
||||
Vec,
|
||||
} from '@blocksuite/global/utils';
|
||||
|
||||
import { DEFAULT_CENTRAL_AREA_RATIO } from '../../../consts/index.js';
|
||||
import type { ShapeElementModel } from '../shape.js';
|
||||
|
||||
export const ellipse = {
|
||||
points({ x, y, w, h }: IBound): IVec[] {
|
||||
return [
|
||||
[x, y + h / 2],
|
||||
[x + w / 2, y],
|
||||
[x + w, y + h / 2],
|
||||
[x + w / 2, y + h],
|
||||
];
|
||||
},
|
||||
draw(ctx: CanvasRenderingContext2D, { x, y, w, h, rotate = 0 }: IBound) {
|
||||
const cx = x + w / 2;
|
||||
const cy = y + h / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
ctx.translate(-cx, -cy);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(cx, cy, w / 2, h / 2, 0, 0, 2 * Math.PI);
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
includesPoint(
|
||||
this: ShapeElementModel,
|
||||
x: number,
|
||||
y: number,
|
||||
options: PointTestOptions
|
||||
) {
|
||||
const point: IVec = [x, y];
|
||||
const expand = (options?.hitThreshold ?? 1) / (options?.zoom ?? 1);
|
||||
const rx = this.w / 2;
|
||||
const ry = this.h / 2;
|
||||
const center: IVec = [this.x + rx, this.y + ry];
|
||||
const rad = (this.rotate * Math.PI) / 180;
|
||||
|
||||
let hit =
|
||||
pointInEllipse(point, center, rx + expand, ry + expand, rad) &&
|
||||
!pointInEllipse(point, center, rx - expand, ry - expand, rad);
|
||||
|
||||
if (!hit) {
|
||||
if (!options.ignoreTransparent || this.filled) {
|
||||
hit = pointInEllipse(point, center, rx, ry, rad);
|
||||
} else {
|
||||
// If shape is not filled or transparent
|
||||
const text = this.text;
|
||||
if (!text || !text.length) {
|
||||
// Check the center area of the shape
|
||||
const centralRx = rx * DEFAULT_CENTRAL_AREA_RATIO;
|
||||
const centralRy = ry * DEFAULT_CENTRAL_AREA_RATIO;
|
||||
hit = pointInEllipse(point, center, centralRx, centralRy, rad);
|
||||
} else if (this.textBound) {
|
||||
hit = pointInPolygon(
|
||||
point,
|
||||
getPointsFromBoundWithRotation(
|
||||
this,
|
||||
() => Bound.from(this.textBound!).points
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
},
|
||||
containsBound(bounds: Bound, element: ShapeElementModel): boolean {
|
||||
const points = getPointsFromBoundWithRotation(element, ellipse.points);
|
||||
return points.some(point => bounds.containsPoint(point));
|
||||
},
|
||||
|
||||
// See links:
|
||||
// * https://github.com/0xfaded/ellipse_demo/issues/1
|
||||
// * https://blog.chatfield.io/simple-method-for-distance-to-ellipse/
|
||||
// * https://gist.github.com/fundon/11331322d3ca223c42e216df48c339e1
|
||||
// * https://github.com/excalidraw/excalidraw/blob/master/packages/utils/geometry/geometry.ts#L888 (MIT)
|
||||
getNearestPoint(point: IVec, { rotate, xywh }: ShapeElementModel) {
|
||||
const { center, w, h } = Bound.deserialize(xywh);
|
||||
const rad = toRadian(rotate);
|
||||
const a = w / 2;
|
||||
const b = h / 2;
|
||||
|
||||
// Use the center of the ellipse as the origin
|
||||
const [rotatedPointX, rotatedPointY] = Vec.rot(
|
||||
Vec.sub(point, center),
|
||||
-rad
|
||||
);
|
||||
|
||||
const px = Math.abs(rotatedPointX);
|
||||
const py = Math.abs(rotatedPointY);
|
||||
|
||||
let tx = Math.SQRT1_2; // 0.707
|
||||
let ty = Math.SQRT1_2; // 0.707
|
||||
let i = 0;
|
||||
|
||||
for (; i < 3; i++) {
|
||||
const x = a * tx;
|
||||
const y = b * ty;
|
||||
|
||||
const ex = ((a * a - b * b) * tx ** 3) / a;
|
||||
const ey = ((b * b - a * a) * ty ** 3) / b;
|
||||
|
||||
const rx = x - ex;
|
||||
const ry = y - ey;
|
||||
|
||||
const qx = px - ex;
|
||||
const qy = py - ey;
|
||||
|
||||
const r = Math.hypot(ry, rx);
|
||||
const q = Math.hypot(qy, qx);
|
||||
|
||||
tx = clamp(((qx * r) / q + ex) / a, 0, 1);
|
||||
ty = clamp(((qy * r) / q + ey) / b, 0, 1);
|
||||
const t = Math.hypot(ty, tx);
|
||||
tx /= t;
|
||||
ty /= t;
|
||||
}
|
||||
|
||||
return Vec.add(
|
||||
Vec.rot(
|
||||
[a * tx * Math.sign(rotatedPointX), b * ty * Math.sign(rotatedPointY)],
|
||||
rad
|
||||
),
|
||||
center
|
||||
);
|
||||
},
|
||||
|
||||
getLineIntersections(
|
||||
start: IVec,
|
||||
end: IVec,
|
||||
{ rotate, xywh }: ShapeElementModel
|
||||
) {
|
||||
const rad = toRadian(rotate);
|
||||
const bound = Bound.deserialize(xywh);
|
||||
return lineEllipseIntersects(
|
||||
start,
|
||||
end,
|
||||
bound.center,
|
||||
bound.w / 2,
|
||||
bound.h / 2,
|
||||
rad
|
||||
);
|
||||
},
|
||||
|
||||
getRelativePointLocation(
|
||||
relativePoint: IVec,
|
||||
{ rotate, xywh }: ShapeElementModel
|
||||
) {
|
||||
const bounds = Bound.deserialize(xywh);
|
||||
const point = bounds.getRelativePoint(relativePoint);
|
||||
const { x, y, w, h, center } = bounds;
|
||||
const points = rotatePoints(
|
||||
[
|
||||
[x, y],
|
||||
[x + w / 2, y],
|
||||
[x + w, y],
|
||||
[x + w, y + h / 2],
|
||||
[x + w, y + h],
|
||||
[x + w / 2, y + h],
|
||||
[x, y + h],
|
||||
[x, y + h / 2],
|
||||
point,
|
||||
],
|
||||
center,
|
||||
rotate
|
||||
);
|
||||
const rotatedPoint = points.pop() as IVec;
|
||||
const len = points.length;
|
||||
let tangent: IVec = [0, 0.5];
|
||||
let i = 0;
|
||||
|
||||
for (; i < len; i++) {
|
||||
const p0 = points[i];
|
||||
const p1 = points[(i + 1) % len];
|
||||
const bounds = Bound.fromPoints([p0, p1, center]);
|
||||
if (bounds.containsPoint(rotatedPoint)) {
|
||||
tangent = Vec.normalize(Vec.sub(p1, p0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new PointLocation(rotatedPoint, tangent);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { ShapeType } from '../../../consts/shape.js';
|
||||
import { diamond } from './diamond.js';
|
||||
import { ellipse } from './ellipse.js';
|
||||
import { rect } from './rect.js';
|
||||
import { triangle } from './triangle.js';
|
||||
|
||||
export const shapeMethods: Record<ShapeType, typeof rect> = {
|
||||
rect,
|
||||
triangle,
|
||||
ellipse,
|
||||
diamond,
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
import type { PointTestOptions } from '@blocksuite/block-std/gfx';
|
||||
import type { IBound, IVec } from '@blocksuite/global/utils';
|
||||
import {
|
||||
Bound,
|
||||
getCenterAreaBounds,
|
||||
getPointsFromBoundWithRotation,
|
||||
linePolygonIntersects,
|
||||
pointInPolygon,
|
||||
PointLocation,
|
||||
pointOnPolygonStoke,
|
||||
polygonGetPointTangent,
|
||||
polygonNearestPoint,
|
||||
rotatePoints,
|
||||
} from '@blocksuite/global/utils';
|
||||
|
||||
import { DEFAULT_CENTRAL_AREA_RATIO } from '../../../consts/index.js';
|
||||
import type { ShapeElementModel } from '../shape.js';
|
||||
|
||||
export const rect = {
|
||||
points({ x, y, w, h }: IBound) {
|
||||
return [
|
||||
[x, y],
|
||||
[x + w, y],
|
||||
[x + w, y + h],
|
||||
[x, y + h],
|
||||
];
|
||||
},
|
||||
draw(ctx: CanvasRenderingContext2D, { x, y, w, h, rotate = 0 }: IBound) {
|
||||
ctx.save();
|
||||
ctx.translate(x + w / 2, y + h / 2);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
ctx.translate(-x - w / 2, -y - h / 2);
|
||||
ctx.rect(x, y, w, h);
|
||||
ctx.restore();
|
||||
},
|
||||
includesPoint(
|
||||
this: ShapeElementModel,
|
||||
x: number,
|
||||
y: number,
|
||||
options: PointTestOptions
|
||||
) {
|
||||
const point: IVec = [x, y];
|
||||
const points = getPointsFromBoundWithRotation(
|
||||
this,
|
||||
undefined,
|
||||
options.responsePadding
|
||||
);
|
||||
|
||||
let hit = pointOnPolygonStoke(
|
||||
point,
|
||||
points,
|
||||
(options?.hitThreshold ?? 1) / (options.zoom ?? 1)
|
||||
);
|
||||
|
||||
if (!hit) {
|
||||
// If the point is not on the stroke, check if it is in the shape
|
||||
// When the shape is filled and transparent is not ignored
|
||||
if (!options.ignoreTransparent || this.filled) {
|
||||
hit = pointInPolygon([x, y], points);
|
||||
} else {
|
||||
// If shape is not filled or transparent
|
||||
// Check if hit the text area
|
||||
const text = this.text;
|
||||
if (!text || !text.length) {
|
||||
// if not, check the default center area of the shape
|
||||
const centralBounds = getCenterAreaBounds(
|
||||
this,
|
||||
DEFAULT_CENTRAL_AREA_RATIO
|
||||
);
|
||||
const centralPoints = getPointsFromBoundWithRotation(centralBounds);
|
||||
// Check if the point is in the center area
|
||||
hit = pointInPolygon([x, y], centralPoints);
|
||||
} else if (this.textBound) {
|
||||
hit = pointInPolygon(
|
||||
point,
|
||||
getPointsFromBoundWithRotation(
|
||||
this,
|
||||
() => Bound.from(this.textBound!).points
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
},
|
||||
|
||||
containsBound(bounds: Bound, element: ShapeElementModel): boolean {
|
||||
const points = getPointsFromBoundWithRotation(element);
|
||||
return points.some(point => bounds.containsPoint(point));
|
||||
},
|
||||
|
||||
getNearestPoint(point: IVec, element: ShapeElementModel) {
|
||||
const points = getPointsFromBoundWithRotation(element);
|
||||
return polygonNearestPoint(points, point);
|
||||
},
|
||||
|
||||
getLineIntersections(start: IVec, end: IVec, element: ShapeElementModel) {
|
||||
const points = getPointsFromBoundWithRotation(element);
|
||||
return linePolygonIntersects(start, end, points);
|
||||
},
|
||||
|
||||
getRelativePointLocation(relativePoint: IVec, element: ShapeElementModel) {
|
||||
const bound = Bound.deserialize(element.xywh);
|
||||
const point = bound.getRelativePoint(relativePoint);
|
||||
const rotatePoint = rotatePoints(
|
||||
[point],
|
||||
bound.center,
|
||||
element.rotate ?? 0
|
||||
)[0];
|
||||
const points = rotatePoints(
|
||||
bound.points,
|
||||
bound.center,
|
||||
element.rotate ?? 0
|
||||
);
|
||||
const tangent = polygonGetPointTangent(points, rotatePoint);
|
||||
return new PointLocation(rotatePoint, tangent);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { PointTestOptions } from '@blocksuite/block-std/gfx';
|
||||
import type { IBound, IVec } from '@blocksuite/global/utils';
|
||||
import {
|
||||
Bound,
|
||||
getCenterAreaBounds,
|
||||
getPointsFromBoundWithRotation,
|
||||
linePolygonIntersects,
|
||||
pointInPolygon,
|
||||
PointLocation,
|
||||
pointOnPolygonStoke,
|
||||
polygonGetPointTangent,
|
||||
polygonNearestPoint,
|
||||
rotatePoints,
|
||||
} from '@blocksuite/global/utils';
|
||||
|
||||
import { DEFAULT_CENTRAL_AREA_RATIO } from '../../../consts/index.js';
|
||||
import type { ShapeElementModel } from '../shape.js';
|
||||
|
||||
export const triangle = {
|
||||
points({ x, y, w, h }: IBound): IVec[] {
|
||||
return [
|
||||
[x, y + h],
|
||||
[x + w / 2, y],
|
||||
[x + w, y + h],
|
||||
];
|
||||
},
|
||||
draw(ctx: CanvasRenderingContext2D, { x, y, w, h, rotate = 0 }: IBound) {
|
||||
const cx = x + w / 2;
|
||||
const cy = y + h / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
ctx.translate(-cx, -cy);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + h);
|
||||
ctx.lineTo(x + w / 2, y);
|
||||
ctx.lineTo(x + w, y + h);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
includesPoint(
|
||||
this: ShapeElementModel,
|
||||
x: number,
|
||||
y: number,
|
||||
options: PointTestOptions
|
||||
) {
|
||||
const point: IVec = [x, y];
|
||||
const points = getPointsFromBoundWithRotation(this, triangle.points);
|
||||
|
||||
let hit = pointOnPolygonStoke(
|
||||
point,
|
||||
points,
|
||||
(options?.hitThreshold ?? 1) / (options?.zoom ?? 1)
|
||||
);
|
||||
|
||||
if (!hit) {
|
||||
if (!options.ignoreTransparent || this.filled) {
|
||||
hit = pointInPolygon([x, y], points);
|
||||
} else {
|
||||
// If shape is not filled or transparent
|
||||
const text = this.text;
|
||||
if (!text || !text.length) {
|
||||
// Check the center area of the shape
|
||||
const centralBounds = getCenterAreaBounds(
|
||||
this,
|
||||
DEFAULT_CENTRAL_AREA_RATIO
|
||||
);
|
||||
const centralPoints = getPointsFromBoundWithRotation(
|
||||
centralBounds,
|
||||
triangle.points
|
||||
);
|
||||
hit = pointInPolygon([x, y], centralPoints);
|
||||
} else if (this.textBound) {
|
||||
hit = pointInPolygon(
|
||||
point,
|
||||
getPointsFromBoundWithRotation(
|
||||
this,
|
||||
() => Bound.from(this.textBound!).points
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
},
|
||||
containsBound(bounds: Bound, element: ShapeElementModel): boolean {
|
||||
const points = getPointsFromBoundWithRotation(element, triangle.points);
|
||||
return points.some(point => bounds.containsPoint(point));
|
||||
},
|
||||
|
||||
getNearestPoint(point: IVec, element: ShapeElementModel) {
|
||||
const points = getPointsFromBoundWithRotation(element, triangle.points);
|
||||
return polygonNearestPoint(points, point);
|
||||
},
|
||||
|
||||
getLineIntersections(start: IVec, end: IVec, element: ShapeElementModel) {
|
||||
const points = getPointsFromBoundWithRotation(element, triangle.points);
|
||||
return linePolygonIntersects(start, end, points);
|
||||
},
|
||||
|
||||
getRelativePointLocation(position: IVec, element: ShapeElementModel) {
|
||||
const bound = Bound.deserialize(element.xywh);
|
||||
const point = bound.getRelativePoint(position);
|
||||
let points = triangle.points(bound);
|
||||
points.push(point);
|
||||
|
||||
points = rotatePoints(points, bound.center, element.rotate);
|
||||
const rotatePoint = points.pop() as IVec;
|
||||
const tangent = polygonGetPointTangent(points, rotatePoint);
|
||||
return new PointLocation(rotatePoint, tangent);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './api/index.js';
|
||||
export * from './shape.js';
|
||||
@@ -0,0 +1,277 @@
|
||||
import type {
|
||||
BaseElementProps,
|
||||
PointTestOptions,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
field,
|
||||
GfxLocalElementModel,
|
||||
GfxPrimitiveElementModel,
|
||||
local,
|
||||
prop,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import type {
|
||||
Bound,
|
||||
IBound,
|
||||
IVec,
|
||||
PointLocation,
|
||||
SerializedXYWH,
|
||||
} from '@blocksuite/global/utils';
|
||||
import { DocCollection, type Y } from '@blocksuite/store';
|
||||
|
||||
import {
|
||||
type Color,
|
||||
DEFAULT_ROUGHNESS,
|
||||
FontFamily,
|
||||
FontStyle,
|
||||
FontWeight,
|
||||
LineColor,
|
||||
ShapeFillColor,
|
||||
ShapeStyle,
|
||||
ShapeTextFontSize,
|
||||
ShapeType,
|
||||
StrokeStyle,
|
||||
TextAlign,
|
||||
TextResizing,
|
||||
type TextStyleProps,
|
||||
TextVerticalAlign,
|
||||
} from '../../consts/index.js';
|
||||
import { shapeMethods } from './api/index.js';
|
||||
|
||||
export type ShapeProps = BaseElementProps & {
|
||||
shapeType: ShapeType;
|
||||
radius: number;
|
||||
filled: boolean;
|
||||
fillColor: Color;
|
||||
strokeWidth: number;
|
||||
strokeColor: Color;
|
||||
strokeStyle: StrokeStyle;
|
||||
shapeStyle: ShapeStyle;
|
||||
// https://github.com/rough-stuff/rough/wiki#roughness
|
||||
roughness?: number;
|
||||
|
||||
text?: Y.Text;
|
||||
textHorizontalAlign?: TextAlign;
|
||||
textVerticalAlign?: TextVerticalAlign;
|
||||
textResizing?: TextResizing;
|
||||
maxWidth?: false | number;
|
||||
} & Partial<TextStyleProps>;
|
||||
|
||||
export const SHAPE_TEXT_PADDING = 20;
|
||||
export const SHAPE_TEXT_VERTICAL_PADDING = 10;
|
||||
|
||||
export class ShapeElementModel extends GfxPrimitiveElementModel<ShapeProps> {
|
||||
/**
|
||||
* The bound of the text content.
|
||||
*/
|
||||
textBound: IBound | null = null;
|
||||
|
||||
get type() {
|
||||
return 'shape';
|
||||
}
|
||||
|
||||
static override propsToY(props: ShapeProps) {
|
||||
if (props.text && !(props.text instanceof DocCollection.Y.Text)) {
|
||||
props.text = new DocCollection.Y.Text(props.text);
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
override containsBound(bounds: Bound) {
|
||||
return shapeMethods[this.shapeType].containsBound(bounds, this);
|
||||
}
|
||||
|
||||
override getLineIntersections(start: IVec, end: IVec) {
|
||||
return shapeMethods[this.shapeType].getLineIntersections(start, end, this);
|
||||
}
|
||||
|
||||
override getNearestPoint(point: IVec): IVec {
|
||||
return shapeMethods[this.shapeType].getNearestPoint(point, this) as IVec;
|
||||
}
|
||||
|
||||
override getRelativePointLocation(point: IVec): PointLocation {
|
||||
return shapeMethods[this.shapeType].getRelativePointLocation(point, this);
|
||||
}
|
||||
|
||||
override includesPoint(x: number, y: number, options: PointTestOptions) {
|
||||
return shapeMethods[this.shapeType].includesPoint.call(this, x, y, {
|
||||
...options,
|
||||
ignoreTransparent: options.ignoreTransparent ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
@field('#000000' as Color)
|
||||
accessor color!: Color;
|
||||
|
||||
@field()
|
||||
accessor fillColor: Color = ShapeFillColor.Yellow;
|
||||
|
||||
@field()
|
||||
accessor filled: boolean = false;
|
||||
|
||||
@field(FontFamily.Inter as string)
|
||||
accessor fontFamily!: string;
|
||||
|
||||
@field(ShapeTextFontSize.MEDIUM)
|
||||
accessor fontSize!: number;
|
||||
|
||||
@field(FontStyle.Normal as FontStyle)
|
||||
accessor fontStyle!: FontStyle;
|
||||
|
||||
@field(FontWeight.Regular as FontWeight)
|
||||
accessor fontWeight!: FontWeight;
|
||||
|
||||
@field(false as false | number)
|
||||
accessor maxWidth: false | number = false;
|
||||
|
||||
@field([SHAPE_TEXT_VERTICAL_PADDING, SHAPE_TEXT_PADDING])
|
||||
accessor padding: [number, number] = [
|
||||
SHAPE_TEXT_VERTICAL_PADDING,
|
||||
SHAPE_TEXT_PADDING,
|
||||
];
|
||||
|
||||
@field()
|
||||
accessor radius: number = 0;
|
||||
|
||||
@field(0)
|
||||
accessor rotate: number = 0;
|
||||
|
||||
@field(DEFAULT_ROUGHNESS)
|
||||
accessor roughness: number = DEFAULT_ROUGHNESS;
|
||||
|
||||
@field()
|
||||
accessor shadow: {
|
||||
/**
|
||||
* @deprecated Since the shadow blur will reduce the performance of canvas rendering,
|
||||
* we already disable the shadow blur rendering by default, so set this field will not take effect.
|
||||
* You can enable it by setting the flag `enable_shape_shadow_blur` in the awareness store.
|
||||
* https://web.dev/articles/canvas-performance#avoid_shadowblur
|
||||
*/
|
||||
blur: number;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
color: string;
|
||||
} | null = null;
|
||||
|
||||
@field()
|
||||
accessor shapeStyle: ShapeStyle = ShapeStyle.General;
|
||||
|
||||
@field()
|
||||
accessor shapeType: ShapeType = ShapeType.Rect;
|
||||
|
||||
@field()
|
||||
accessor strokeColor: Color = LineColor.Yellow;
|
||||
|
||||
@field()
|
||||
accessor strokeStyle: StrokeStyle = StrokeStyle.Solid;
|
||||
|
||||
@field()
|
||||
accessor strokeWidth: number = 4;
|
||||
|
||||
@field()
|
||||
accessor text: Y.Text | undefined = undefined;
|
||||
|
||||
@field(TextAlign.Center as TextAlign)
|
||||
accessor textAlign!: TextAlign;
|
||||
|
||||
@local()
|
||||
accessor textDisplay: boolean = true;
|
||||
|
||||
@field(TextAlign.Center as TextAlign)
|
||||
accessor textHorizontalAlign!: TextAlign;
|
||||
|
||||
@field(TextResizing.AUTO_HEIGHT as TextResizing)
|
||||
accessor textResizing: TextResizing = TextResizing.AUTO_HEIGHT;
|
||||
|
||||
@field(TextVerticalAlign.Center as TextVerticalAlign)
|
||||
accessor textVerticalAlign!: TextVerticalAlign;
|
||||
|
||||
@field()
|
||||
accessor xywh: SerializedXYWH = '[0,0,100,100]';
|
||||
}
|
||||
|
||||
export class LocalShapeElementModel extends GfxLocalElementModel {
|
||||
roughness: number = DEFAULT_ROUGHNESS;
|
||||
|
||||
textBound: Bound | null = null;
|
||||
|
||||
textDisplay: boolean = true;
|
||||
|
||||
get type() {
|
||||
return 'shape';
|
||||
}
|
||||
|
||||
@prop()
|
||||
accessor color: Color = '#000000';
|
||||
|
||||
@prop()
|
||||
accessor fillColor: Color = ShapeFillColor.Yellow;
|
||||
|
||||
@prop()
|
||||
accessor filled: boolean = false;
|
||||
|
||||
@prop()
|
||||
accessor fontFamily: string = FontFamily.Inter;
|
||||
|
||||
@prop()
|
||||
accessor fontSize: number = 16;
|
||||
|
||||
@prop()
|
||||
accessor fontStyle: FontStyle = FontStyle.Normal;
|
||||
|
||||
@prop()
|
||||
accessor fontWeight: FontWeight = FontWeight.Regular;
|
||||
|
||||
@prop()
|
||||
accessor padding: [number, number] = [
|
||||
SHAPE_TEXT_VERTICAL_PADDING,
|
||||
SHAPE_TEXT_PADDING,
|
||||
];
|
||||
|
||||
@prop()
|
||||
accessor radius: number = 0;
|
||||
|
||||
@prop()
|
||||
accessor shadow: {
|
||||
blur: number;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
color: string;
|
||||
} | null = null;
|
||||
|
||||
@prop()
|
||||
accessor shapeStyle: ShapeStyle = ShapeStyle.General;
|
||||
|
||||
@prop()
|
||||
accessor shapeType: ShapeType = ShapeType.Rect;
|
||||
|
||||
@prop()
|
||||
accessor strokeColor: Color = LineColor.Yellow;
|
||||
|
||||
@prop()
|
||||
accessor strokeStyle: StrokeStyle = StrokeStyle.Solid;
|
||||
|
||||
@prop()
|
||||
accessor strokeWidth: number = 4;
|
||||
|
||||
@prop()
|
||||
accessor text: string = '';
|
||||
|
||||
@prop()
|
||||
accessor textAlign: TextAlign = TextAlign.Center;
|
||||
|
||||
@prop()
|
||||
accessor textVerticalAlign: TextVerticalAlign = TextVerticalAlign.Center;
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace BlockSuite {
|
||||
interface SurfaceElementModelMap {
|
||||
shape: ShapeElementModel;
|
||||
}
|
||||
|
||||
interface EdgelessTextModelMap {
|
||||
shape: ShapeElementModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user