@canvas-commons/2d 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/editor/index.css +40 -0
- package/editor/index.js +525 -0
- package/editor/index.js.map +1 -0
- package/lib/index-OvpZ-GsA.d.ts +5673 -0
- package/lib/index-OvpZ-GsA.d.ts.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +10326 -0
- package/lib/index.js.map +1 -0
- package/lib/jsx-dev-runtime.d.ts +2 -0
- package/lib/jsx-dev-runtime.js +2 -0
- package/lib/jsx-runtime.d.ts +2 -0
- package/lib/jsx-runtime.js +28 -0
- package/lib/jsx-runtime.js.map +1 -0
- package/package.json +79 -0
- package/src/editor/NodeInspectorConfig.tsx +76 -0
- package/src/editor/PreviewOverlayConfig.tsx +65 -0
- package/src/editor/Provider.tsx +109 -0
- package/src/editor/SceneGraphTabConfig.tsx +87 -0
- package/src/editor/icons/CircleIcon.tsx +7 -0
- package/src/editor/icons/CodeIcon.tsx +8 -0
- package/src/editor/icons/CurveIcon.tsx +7 -0
- package/src/editor/icons/GridIcon.tsx +7 -0
- package/src/editor/icons/IconMap.ts +35 -0
- package/src/editor/icons/ImgIcon.tsx +8 -0
- package/src/editor/icons/LayoutIcon.tsx +9 -0
- package/src/editor/icons/LineIcon.tsx +7 -0
- package/src/editor/icons/NodeIcon.tsx +7 -0
- package/src/editor/icons/RayIcon.tsx +7 -0
- package/src/editor/icons/RectIcon.tsx +7 -0
- package/src/editor/icons/ShapeIcon.tsx +7 -0
- package/src/editor/icons/TxtIcon.tsx +8 -0
- package/src/editor/icons/VideoIcon.tsx +7 -0
- package/src/editor/icons/View2DIcon.tsx +10 -0
- package/src/editor/index.css +0 -0
- package/src/editor/index.ts +19 -0
- package/src/editor/shortcuts.ts +27 -0
- package/src/editor/tree/DetachedRoot.tsx +27 -0
- package/src/editor/tree/NodeElement.tsx +72 -0
- package/src/editor/tree/TreeElement.tsx +70 -0
- package/src/editor/tree/TreeRoot.tsx +10 -0
- package/src/editor/tree/ViewRoot.tsx +20 -0
- package/src/editor/tree/index.module.scss +45 -0
- package/src/editor/tree/index.ts +4 -0
- package/src/editor/tree/navigation.ts +145 -0
- package/src/editor/tsconfig.build.json +5 -0
- package/src/editor/tsconfig.json +12 -0
- package/src/editor/tsdoc.json +4 -0
- package/src/editor/utils/SignalSet.ts +37 -0
- package/src/editor/utils/index.ts +1 -0
- package/src/editor/vite-env.d.ts +1 -0
- package/src/lib/code/CodeCursor.ts +468 -0
- package/src/lib/code/CodeDiffer.ts +77 -0
- package/src/lib/code/CodeFragment.ts +96 -0
- package/src/lib/code/CodeHighlighter.ts +73 -0
- package/src/lib/code/CodeMetrics.ts +47 -0
- package/src/lib/code/CodeRange.test.ts +113 -0
- package/src/lib/code/CodeRange.ts +222 -0
- package/src/lib/code/CodeScope.ts +100 -0
- package/src/lib/code/CodeSelection.ts +28 -0
- package/src/lib/code/CodeSignal.ts +348 -0
- package/src/lib/code/CodeTokenizer.ts +54 -0
- package/src/lib/code/DefaultHighlightStyle.ts +98 -0
- package/src/lib/code/LezerHighlighter.ts +113 -0
- package/src/lib/code/diff.test.ts +311 -0
- package/src/lib/code/diff.ts +319 -0
- package/src/lib/code/extractRange.ts +125 -0
- package/src/lib/code/index.ts +13 -0
- package/src/lib/components/Bezier.ts +103 -0
- package/src/lib/components/Camera.ts +401 -0
- package/src/lib/components/Circle.ts +310 -0
- package/src/lib/components/Code.ts +532 -0
- package/src/lib/components/CubicBezier.ts +115 -0
- package/src/lib/components/Curve.ts +460 -0
- package/src/lib/components/Grid.ts +134 -0
- package/src/lib/components/Icon.ts +153 -0
- package/src/lib/components/Img.ts +328 -0
- package/src/lib/components/Knot.ts +156 -0
- package/src/lib/components/Latex.ts +537 -0
- package/src/lib/components/Layout.ts +1101 -0
- package/src/lib/components/Line.ts +394 -0
- package/src/lib/components/Node.ts +1941 -0
- package/src/lib/components/Path.ts +132 -0
- package/src/lib/components/Polygon.ts +266 -0
- package/src/lib/components/QuadBezier.ts +103 -0
- package/src/lib/components/Ray.ts +126 -0
- package/src/lib/components/Rect.ts +219 -0
- package/src/lib/components/SVG.ts +853 -0
- package/src/lib/components/Shape.ts +322 -0
- package/src/lib/components/Spline.ts +318 -0
- package/src/lib/components/Txt.test.tsx +81 -0
- package/src/lib/components/Txt.ts +204 -0
- package/src/lib/components/TxtLeaf.ts +210 -0
- package/src/lib/components/Video.ts +368 -0
- package/src/lib/components/View2D.ts +85 -0
- package/src/lib/components/__logs__/image-without-source.ts +18 -0
- package/src/lib/components/__logs__/line-without-points.ts +31 -0
- package/src/lib/components/__logs__/reactive-playback-rate.ts +22 -0
- package/src/lib/components/__logs__/spline-with-insufficient-knots.ts +25 -0
- package/src/lib/components/__tests__/Camera.test.tsx +73 -0
- package/src/lib/components/__tests__/children.test.tsx +142 -0
- package/src/lib/components/__tests__/clone.test.tsx +126 -0
- package/src/lib/components/__tests__/fontInheritance.test.tsx +102 -0
- package/src/lib/components/__tests__/generatorTest.ts +27 -0
- package/src/lib/components/__tests__/mockScene2D.ts +50 -0
- package/src/lib/components/__tests__/query.test.tsx +122 -0
- package/src/lib/components/__tests__/state.test.tsx +60 -0
- package/src/lib/components/index.ts +26 -0
- package/src/lib/components/types.ts +35 -0
- package/src/lib/curves/ArcSegment.ts +169 -0
- package/src/lib/curves/CircleSegment.ts +99 -0
- package/src/lib/curves/CubicBezierSegment.ts +80 -0
- package/src/lib/curves/CurveDrawingInfo.ts +11 -0
- package/src/lib/curves/CurvePoint.ts +15 -0
- package/src/lib/curves/CurveProfile.ts +28 -0
- package/src/lib/curves/KnotInfo.ts +10 -0
- package/src/lib/curves/LineSegment.ts +75 -0
- package/src/lib/curves/Polynomial.ts +355 -0
- package/src/lib/curves/Polynomial2D.ts +62 -0
- package/src/lib/curves/PolynomialSegment.ts +151 -0
- package/src/lib/curves/QuadBezierSegment.ts +66 -0
- package/src/lib/curves/Segment.ts +31 -0
- package/src/lib/curves/UniformPolynomialCurveSampler.ts +93 -0
- package/src/lib/curves/createCurveProfileLerp.ts +471 -0
- package/src/lib/curves/getBezierSplineProfile.ts +227 -0
- package/src/lib/curves/getCircleProfile.ts +86 -0
- package/src/lib/curves/getPathProfile.ts +177 -0
- package/src/lib/curves/getPointAtDistance.ts +21 -0
- package/src/lib/curves/getPolylineProfile.test.ts +21 -0
- package/src/lib/curves/getPolylineProfile.ts +88 -0
- package/src/lib/curves/getRectProfile.ts +138 -0
- package/src/lib/curves/index.ts +16 -0
- package/src/lib/decorators/canvasStyleSignal.ts +15 -0
- package/src/lib/decorators/colorSignal.ts +9 -0
- package/src/lib/decorators/compound.ts +85 -0
- package/src/lib/decorators/computed.ts +18 -0
- package/src/lib/decorators/defaultStyle.ts +15 -0
- package/src/lib/decorators/filtersSignal.ts +133 -0
- package/src/lib/decorators/index.ts +10 -0
- package/src/lib/decorators/initializers.ts +32 -0
- package/src/lib/decorators/nodeName.ts +13 -0
- package/src/lib/decorators/signal.test.ts +89 -0
- package/src/lib/decorators/signal.ts +348 -0
- package/src/lib/decorators/spacingSignal.ts +15 -0
- package/src/lib/decorators/transformSignals.test.ts +858 -0
- package/src/lib/decorators/transformSignals.ts +1633 -0
- package/src/lib/decorators/vector2Signal.ts +35 -0
- package/src/lib/globals.d.ts +2 -0
- package/src/lib/index.ts +9 -0
- package/src/lib/jsx-dev-runtime.ts +2 -0
- package/src/lib/jsx-runtime.ts +45 -0
- package/src/lib/morphers/PathMorpher.ts +8 -0
- package/src/lib/morphers/defaultMorpher.ts +43 -0
- package/src/lib/morphers/index.ts +4 -0
- package/src/lib/morphers/manimMorpher.ts +658 -0
- package/src/lib/parse-svg-path.d.ts +14 -0
- package/src/lib/partials/Filter.ts +185 -0
- package/src/lib/partials/Gradient.ts +103 -0
- package/src/lib/partials/Pattern.ts +35 -0
- package/src/lib/partials/RoughConfig.ts +180 -0
- package/src/lib/partials/ShaderConfig.ts +122 -0
- package/src/lib/partials/index.ts +5 -0
- package/src/lib/partials/types.ts +58 -0
- package/src/lib/scenes/Scene2D.ts +167 -0
- package/src/lib/scenes/index.ts +3 -0
- package/src/lib/scenes/makeScene2D.ts +19 -0
- package/src/lib/scenes/useScene2D.ts +6 -0
- package/src/lib/tsconfig.build.json +5 -0
- package/src/lib/tsconfig.json +11 -0
- package/src/lib/tsdoc.json +4 -0
- package/src/lib/utils/CanvasUtils.ts +304 -0
- package/src/lib/utils/PathDataBuilder.ts +320 -0
- package/src/lib/utils/diff.test.ts +453 -0
- package/src/lib/utils/diff.ts +148 -0
- package/src/lib/utils/index.ts +5 -0
- package/src/lib/utils/is.ts +11 -0
- package/src/lib/utils/makeSignalExtensions.ts +29 -0
- package/src/lib/utils/rough.ts +513 -0
- package/src/lib/utils/withDefaults.tsx +26 -0
- package/src/tsconfig.base.json +18 -0
- package/src/tsconfig.build.json +8 -0
- package/src/tsconfig.json +5 -0
- package/tsconfig.project.json +7 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BBox,
|
|
3
|
+
createSignal,
|
|
4
|
+
isReactive,
|
|
5
|
+
SignalValue,
|
|
6
|
+
SimpleSignal,
|
|
7
|
+
threadable,
|
|
8
|
+
TimingFunction,
|
|
9
|
+
tween,
|
|
10
|
+
Vector2,
|
|
11
|
+
} from '@canvas-commons/core';
|
|
12
|
+
import {CurveProfile} from '../curves';
|
|
13
|
+
import {createCurveProfileLerp} from '../curves/createCurveProfileLerp';
|
|
14
|
+
import {getPathProfile} from '../curves/getPathProfile';
|
|
15
|
+
import {computed, signal} from '../decorators';
|
|
16
|
+
import {drawLine, drawPivot} from '../utils';
|
|
17
|
+
import {Curve, CurveProps} from './Curve';
|
|
18
|
+
|
|
19
|
+
export interface PathProps extends CurveProps {
|
|
20
|
+
data: SignalValue<string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Path extends Curve {
|
|
24
|
+
private currentProfile = createSignal<CurveProfile | null>(null);
|
|
25
|
+
@signal()
|
|
26
|
+
declare public readonly data: SimpleSignal<string, this>;
|
|
27
|
+
|
|
28
|
+
public constructor(props: PathProps) {
|
|
29
|
+
super(props);
|
|
30
|
+
this.canHaveSubpath = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@computed()
|
|
34
|
+
public override profile(): CurveProfile {
|
|
35
|
+
return this.currentProfile() ?? getPathProfile(this.data());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected override childrenBBox() {
|
|
39
|
+
const points = this.profile().segments.flatMap(segment => segment.points);
|
|
40
|
+
return BBox.fromPoints(...points);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected override lineWidthCoefficient(): number {
|
|
44
|
+
const join = this.lineJoin();
|
|
45
|
+
|
|
46
|
+
let coefficient = super.lineWidthCoefficient();
|
|
47
|
+
|
|
48
|
+
if (join === 'miter') {
|
|
49
|
+
const {minSin} = this.profile();
|
|
50
|
+
if (minSin > 0) {
|
|
51
|
+
coefficient = Math.max(coefficient, 0.5 / minSin);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return coefficient;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected override processSubpath(
|
|
59
|
+
path: Path2D,
|
|
60
|
+
startPoint: Vector2 | null,
|
|
61
|
+
endPoint: Vector2 | null,
|
|
62
|
+
): void {
|
|
63
|
+
if (startPoint && endPoint && startPoint.equals(endPoint)) {
|
|
64
|
+
path.closePath();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@threadable()
|
|
69
|
+
protected *tweenData(
|
|
70
|
+
newPath: SignalValue<string>,
|
|
71
|
+
time: number,
|
|
72
|
+
timingFunction: TimingFunction,
|
|
73
|
+
) {
|
|
74
|
+
const fromProfile = this.profile();
|
|
75
|
+
const toProfile = getPathProfile(isReactive(newPath) ? newPath() : newPath);
|
|
76
|
+
|
|
77
|
+
const interpolator = createCurveProfileLerp(fromProfile, toProfile);
|
|
78
|
+
|
|
79
|
+
this.currentProfile(fromProfile);
|
|
80
|
+
yield* tween(
|
|
81
|
+
time,
|
|
82
|
+
value => {
|
|
83
|
+
const progress = timingFunction(value);
|
|
84
|
+
this.currentProfile(interpolator(progress));
|
|
85
|
+
},
|
|
86
|
+
() => {
|
|
87
|
+
this.currentProfile(null);
|
|
88
|
+
this.data(newPath);
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public override drawOverlay(
|
|
94
|
+
context: CanvasRenderingContext2D,
|
|
95
|
+
matrix: DOMMatrix,
|
|
96
|
+
): void {
|
|
97
|
+
const box = this.childrenBBox().transformCorners(matrix);
|
|
98
|
+
const size = this.computedSize();
|
|
99
|
+
const offset = size.mul(this.anchor()).scale(0.5).transformAsPoint(matrix);
|
|
100
|
+
const segments = this.profile().segments;
|
|
101
|
+
|
|
102
|
+
context.lineWidth = 1;
|
|
103
|
+
context.strokeStyle = 'white';
|
|
104
|
+
context.fillStyle = 'white';
|
|
105
|
+
|
|
106
|
+
context.save();
|
|
107
|
+
context.setTransform(matrix);
|
|
108
|
+
let endPoint: Vector2 | null = null;
|
|
109
|
+
let path = new Path2D();
|
|
110
|
+
|
|
111
|
+
for (const segment of segments) {
|
|
112
|
+
if (endPoint && !segment.getPoint(0).position.equals(endPoint)) {
|
|
113
|
+
context.stroke(path);
|
|
114
|
+
path = new Path2D();
|
|
115
|
+
endPoint = null;
|
|
116
|
+
}
|
|
117
|
+
const [, end] = segment.draw(path, 0, 1, endPoint == null);
|
|
118
|
+
endPoint = end.position;
|
|
119
|
+
}
|
|
120
|
+
context.stroke(path);
|
|
121
|
+
context.restore();
|
|
122
|
+
|
|
123
|
+
context.beginPath();
|
|
124
|
+
drawPivot(context, offset);
|
|
125
|
+
context.stroke();
|
|
126
|
+
|
|
127
|
+
context.beginPath();
|
|
128
|
+
drawLine(context, box);
|
|
129
|
+
context.closePath();
|
|
130
|
+
context.stroke();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BBox,
|
|
3
|
+
SerializedVector2,
|
|
4
|
+
SignalValue,
|
|
5
|
+
SimpleSignal,
|
|
6
|
+
Vector2,
|
|
7
|
+
} from '@canvas-commons/core';
|
|
8
|
+
import {CurveProfile, getPolylineProfile} from '../curves';
|
|
9
|
+
import {computed, initial, signal} from '../decorators';
|
|
10
|
+
import {DesiredLength} from '../partials';
|
|
11
|
+
import {PathDataBuilder, drawPolygon} from '../utils';
|
|
12
|
+
import {Curve, CurveProps} from './Curve';
|
|
13
|
+
|
|
14
|
+
export interface PolygonProps extends CurveProps {
|
|
15
|
+
/**
|
|
16
|
+
* {@inheritDoc Polygon.sides}
|
|
17
|
+
*/
|
|
18
|
+
sides?: SignalValue<number>;
|
|
19
|
+
/**
|
|
20
|
+
* {@inheritDoc Polygon.radius}
|
|
21
|
+
*/
|
|
22
|
+
radius?: SignalValue<number>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A node for drawing regular polygons.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* This node can be used to render shapes such as: triangle, pentagon,
|
|
30
|
+
* hexagon and more.
|
|
31
|
+
*
|
|
32
|
+
* Note that the polygon is inscribed in a circle defined by the height
|
|
33
|
+
* and width. If height and width are unequal, the polygon is inscribed
|
|
34
|
+
* in the resulting ellipse.
|
|
35
|
+
*
|
|
36
|
+
* Since the polygon is inscribed in the circle, the actual displayed
|
|
37
|
+
* height and width may differ somewhat from the bounding rectangle. This
|
|
38
|
+
* will be particularly noticeable if the number of sides is low, e.g. for a
|
|
39
|
+
* triangle.
|
|
40
|
+
*
|
|
41
|
+
* @preview
|
|
42
|
+
* ```tsx editor
|
|
43
|
+
* // snippet Polygon
|
|
44
|
+
* import {makeScene2D, Polygon} from '@canvas-commons/2d';
|
|
45
|
+
* import {createRef} from '@canvas-commons/core';
|
|
46
|
+
*
|
|
47
|
+
* export default makeScene2D(function* (view) {
|
|
48
|
+
* const ref = createRef<Polygon>();
|
|
49
|
+
* view.add(
|
|
50
|
+
* <Polygon
|
|
51
|
+
* ref={ref}
|
|
52
|
+
* sides={6}
|
|
53
|
+
* size={160}
|
|
54
|
+
* fill={'lightseagreen'}
|
|
55
|
+
* />
|
|
56
|
+
* );
|
|
57
|
+
*
|
|
58
|
+
* yield* ref().sides(3, 2).to(6, 2);
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* // snippet Pentagon outline
|
|
62
|
+
* import {makeScene2D, Polygon} from '@canvas-commons/2d';
|
|
63
|
+
*
|
|
64
|
+
* export default makeScene2D(function* (view) {
|
|
65
|
+
* view.add(
|
|
66
|
+
* <Polygon
|
|
67
|
+
* sides={5}
|
|
68
|
+
* size={160}
|
|
69
|
+
* radius={30}
|
|
70
|
+
* stroke={'lightblue'}
|
|
71
|
+
* lineWidth={8}
|
|
72
|
+
* />
|
|
73
|
+
* );
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* // snippet Accessing vertex data
|
|
77
|
+
* import {Circle, Polygon, makeScene2D} from '@canvas-commons/2d';
|
|
78
|
+
* import {createRef, range} from '@canvas-commons/core';
|
|
79
|
+
*
|
|
80
|
+
* export default makeScene2D(function* (view) {
|
|
81
|
+
* const polygon = createRef<Polygon>();
|
|
82
|
+
* view.add(
|
|
83
|
+
* <Polygon ref={polygon} sides={3} lineWidth={4} stroke={'white'} size={160}>
|
|
84
|
+
* {range(6).map(index => (
|
|
85
|
+
* <Circle
|
|
86
|
+
* fill={'white'}
|
|
87
|
+
* size={20}
|
|
88
|
+
* position={() => polygon().vertex(index)}
|
|
89
|
+
* opacity={() => polygon().vertexCompletion(index)}
|
|
90
|
+
* />
|
|
91
|
+
* ))}
|
|
92
|
+
* </Polygon>,
|
|
93
|
+
* );
|
|
94
|
+
*
|
|
95
|
+
* yield* polygon().sides(6, 2).wait(0.5).back(2);
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export class Polygon extends Curve {
|
|
100
|
+
/**
|
|
101
|
+
* The number of sides of the polygon.
|
|
102
|
+
*
|
|
103
|
+
* @remarks
|
|
104
|
+
* For example, a value of 6 creates a hexagon.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```tsx
|
|
108
|
+
* <Polygon
|
|
109
|
+
* size={320}
|
|
110
|
+
* sides={7}
|
|
111
|
+
* stroke={'#fff'}
|
|
112
|
+
* lineWidth={8}
|
|
113
|
+
* fill={'lightseagreen'}
|
|
114
|
+
* />
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
@initial(6)
|
|
118
|
+
@signal()
|
|
119
|
+
declare public readonly sides: SimpleSignal<number, this>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The radius of the polygon's corners.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```tsx
|
|
126
|
+
* <Polygon
|
|
127
|
+
* radius={30}
|
|
128
|
+
* size={320}
|
|
129
|
+
* sides={3}
|
|
130
|
+
* stroke={'#fff'}
|
|
131
|
+
* lineWidth={8}
|
|
132
|
+
* />
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
@initial(0)
|
|
136
|
+
@signal()
|
|
137
|
+
declare public readonly radius: SimpleSignal<number, this>;
|
|
138
|
+
|
|
139
|
+
public constructor(props: PolygonProps) {
|
|
140
|
+
super(props);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get the position of the nth vertex in the local space of this polygon.
|
|
145
|
+
*
|
|
146
|
+
* @param index - The index of the vertex.
|
|
147
|
+
*/
|
|
148
|
+
public vertex(index: number): Vector2 {
|
|
149
|
+
const size = this.computedSize().scale(0.5);
|
|
150
|
+
const theta = (index * 2 * Math.PI) / this.sides();
|
|
151
|
+
const direction = Vector2.fromRadians(theta).perpendicular;
|
|
152
|
+
return direction.mul(size);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get the completion of the nth vertex.
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
159
|
+
* The completion is a value between `0` and `1` that describes how the given
|
|
160
|
+
* vertex partakes in the polygon.
|
|
161
|
+
*
|
|
162
|
+
* For integer values of {@link sides}, the completion is simply `1` for
|
|
163
|
+
* each index making up the polygon and `0` for any other index. If `sides`
|
|
164
|
+
* includes a fraction, the last index of the polygon will have a completion
|
|
165
|
+
* equal to said fraction.
|
|
166
|
+
*
|
|
167
|
+
* Check out the {@link Polygon | Accessing vertex data} example for a
|
|
168
|
+
* demonstration.
|
|
169
|
+
*
|
|
170
|
+
* @param index - The index of the vertex.
|
|
171
|
+
*/
|
|
172
|
+
public vertexCompletion(index: number): number {
|
|
173
|
+
const sides = this.sides();
|
|
174
|
+
if (index < 0 || index > sides) {
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (index < sides - 1) {
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return sides - index;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@computed()
|
|
186
|
+
public override profile(): CurveProfile {
|
|
187
|
+
const sides = this.sides();
|
|
188
|
+
const radius = this.radius();
|
|
189
|
+
|
|
190
|
+
const points = [];
|
|
191
|
+
const size = this.computedSize().scale(0.5);
|
|
192
|
+
for (let i = 0; i < sides; i++) {
|
|
193
|
+
const theta = (i * 2 * Math.PI) / sides;
|
|
194
|
+
const direction = Vector2.fromRadians(theta).perpendicular;
|
|
195
|
+
points.push(direction.mul(size));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return getPolylineProfile(points, radius, true);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
protected override desiredSize(): SerializedVector2<DesiredLength> {
|
|
202
|
+
return {
|
|
203
|
+
x: this.width.context.getter(),
|
|
204
|
+
y: this.height.context.getter(),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected override offsetComputedLayout(box: BBox): BBox {
|
|
209
|
+
return box;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
protected override childrenBBox(): BBox {
|
|
213
|
+
return BBox.fromSizeCentered(this.computedSize());
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
protected override requiresProfile(): boolean {
|
|
217
|
+
return super.requiresProfile() || this.radius() > 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@computed()
|
|
221
|
+
protected override getPathData(): string {
|
|
222
|
+
const builder = new PathDataBuilder();
|
|
223
|
+
const sides = this.sides();
|
|
224
|
+
const size = this.computedSize().scale(0.5);
|
|
225
|
+
|
|
226
|
+
for (let i = 0; i < sides; i++) {
|
|
227
|
+
const theta = (i * 2 * Math.PI) / sides;
|
|
228
|
+
const direction = Vector2.fromRadians(theta).perpendicular;
|
|
229
|
+
const point = direction.mul(size);
|
|
230
|
+
|
|
231
|
+
if (i === 0) {
|
|
232
|
+
builder.moveTo(point.x, point.y);
|
|
233
|
+
} else {
|
|
234
|
+
builder.lineTo(point.x, point.y);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
builder.closePath();
|
|
239
|
+
return builder.toString();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
protected override getPath(): Path2D {
|
|
243
|
+
if (this.requiresProfile()) {
|
|
244
|
+
return this.curveDrawingInfo().path;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const pathData = this.getPathData();
|
|
248
|
+
if (pathData) {
|
|
249
|
+
return new Path2D(pathData);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return this.createPath();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
protected override getRipplePath(): Path2D {
|
|
256
|
+
return this.createPath(this.rippleSize());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
protected createPath(expand = 0) {
|
|
260
|
+
const path = new Path2D();
|
|
261
|
+
const sides = this.sides();
|
|
262
|
+
const box = BBox.fromSizeCentered(this.size()).expand(expand);
|
|
263
|
+
drawPolygon(path, box, sides);
|
|
264
|
+
return path;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PossibleVector2,
|
|
3
|
+
SignalValue,
|
|
4
|
+
Vector2Signal,
|
|
5
|
+
} from '@canvas-commons/core';
|
|
6
|
+
import {QuadBezierSegment} from '../curves';
|
|
7
|
+
import {PolynomialSegment} from '../curves/PolynomialSegment';
|
|
8
|
+
import {computed, vector2Signal} from '../decorators';
|
|
9
|
+
import {lineTo, moveTo, quadraticCurveTo} from '../utils';
|
|
10
|
+
import {Bezier, BezierOverlayInfo} from './Bezier';
|
|
11
|
+
import {CurveProps} from './Curve';
|
|
12
|
+
|
|
13
|
+
export interface QuadBezierProps extends CurveProps {
|
|
14
|
+
p0?: SignalValue<PossibleVector2>;
|
|
15
|
+
p0X?: SignalValue<number>;
|
|
16
|
+
p0Y?: SignalValue<number>;
|
|
17
|
+
|
|
18
|
+
p1?: SignalValue<PossibleVector2>;
|
|
19
|
+
p1X?: SignalValue<number>;
|
|
20
|
+
p1Y?: SignalValue<number>;
|
|
21
|
+
|
|
22
|
+
p2?: SignalValue<PossibleVector2>;
|
|
23
|
+
p2X?: SignalValue<number>;
|
|
24
|
+
p2Y?: SignalValue<number>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A node for drawing a quadratic Bézier curve.
|
|
29
|
+
*
|
|
30
|
+
* @preview
|
|
31
|
+
* ```tsx editor
|
|
32
|
+
* import {makeScene2D, QuadBezier} from '@canvas-commons/2d';
|
|
33
|
+
* import {createRef} from '@canvas-commons/core';
|
|
34
|
+
*
|
|
35
|
+
* export default makeScene2D(function* (view) {
|
|
36
|
+
* const bezier = createRef<QuadBezier>();
|
|
37
|
+
*
|
|
38
|
+
* view.add(
|
|
39
|
+
* <QuadBezier
|
|
40
|
+
* ref={bezier}
|
|
41
|
+
* lineWidth={4}
|
|
42
|
+
* stroke={'lightseagreen'}
|
|
43
|
+
* p0={[-200, 0]}
|
|
44
|
+
* p1={[0, -200]}
|
|
45
|
+
* p2={[200, 0]}
|
|
46
|
+
* end={0}
|
|
47
|
+
* />
|
|
48
|
+
* );
|
|
49
|
+
*
|
|
50
|
+
* yield* bezier().end(1, 1);
|
|
51
|
+
* yield* bezier().start(1, 1).to(0, 1);
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export class QuadBezier extends Bezier {
|
|
56
|
+
/**
|
|
57
|
+
* The start point of the Bézier curve.
|
|
58
|
+
*/
|
|
59
|
+
@vector2Signal('p0')
|
|
60
|
+
declare public readonly p0: Vector2Signal<this>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The control point of the Bézier curve.
|
|
64
|
+
*/
|
|
65
|
+
@vector2Signal('p1')
|
|
66
|
+
declare public readonly p1: Vector2Signal<this>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The end point of the Bézier curve.
|
|
70
|
+
*/
|
|
71
|
+
@vector2Signal('p2')
|
|
72
|
+
declare public readonly p2: Vector2Signal<this>;
|
|
73
|
+
|
|
74
|
+
public constructor(props: QuadBezierProps) {
|
|
75
|
+
super(props);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@computed()
|
|
79
|
+
protected segment(): PolynomialSegment {
|
|
80
|
+
return new QuadBezierSegment(this.p0(), this.p1(), this.p2());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected overlayInfo(matrix: DOMMatrix): BezierOverlayInfo {
|
|
84
|
+
const [p0, p1, p2] = this.segment().transformPoints(matrix);
|
|
85
|
+
|
|
86
|
+
const curvePath = new Path2D();
|
|
87
|
+
moveTo(curvePath, p0);
|
|
88
|
+
quadraticCurveTo(curvePath, p1, p2);
|
|
89
|
+
|
|
90
|
+
const handleLinesPath = new Path2D();
|
|
91
|
+
moveTo(handleLinesPath, p0);
|
|
92
|
+
lineTo(handleLinesPath, p1);
|
|
93
|
+
lineTo(handleLinesPath, p2);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
curve: curvePath,
|
|
97
|
+
startPoint: p0,
|
|
98
|
+
endPoint: p2,
|
|
99
|
+
controlPoints: [p1],
|
|
100
|
+
handleLines: handleLinesPath,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BBox,
|
|
3
|
+
PossibleVector2,
|
|
4
|
+
SignalValue,
|
|
5
|
+
Vector2Signal,
|
|
6
|
+
} from '@canvas-commons/core';
|
|
7
|
+
import {CurveProfile, LineSegment} from '../curves';
|
|
8
|
+
import {nodeName, vector2Signal} from '../decorators';
|
|
9
|
+
import {arc, drawLine, drawPivot} from '../utils';
|
|
10
|
+
import {Curve, CurveProps} from './Curve';
|
|
11
|
+
|
|
12
|
+
export interface RayProps extends CurveProps {
|
|
13
|
+
/**
|
|
14
|
+
* {@inheritDoc Ray.from}
|
|
15
|
+
*/
|
|
16
|
+
from?: SignalValue<PossibleVector2>;
|
|
17
|
+
fromX?: SignalValue<number>;
|
|
18
|
+
fromY?: SignalValue<number>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* {@inheritDoc Ray.to}
|
|
22
|
+
*/
|
|
23
|
+
to?: SignalValue<PossibleVector2>;
|
|
24
|
+
toX?: SignalValue<number>;
|
|
25
|
+
toY?: SignalValue<number>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A node for drawing an individual line segment.
|
|
30
|
+
*
|
|
31
|
+
* @preview
|
|
32
|
+
* ```tsx editor
|
|
33
|
+
* import {makeScene2D} from '@canvas-commons/2d';
|
|
34
|
+
* import {Ray} from '@canvas-commons/2d';
|
|
35
|
+
* import {createRef} from '@canvas-commons/core';
|
|
36
|
+
*
|
|
37
|
+
* export default makeScene2D(function* (view) {
|
|
38
|
+
* const ray = createRef<Ray>();
|
|
39
|
+
*
|
|
40
|
+
* view.add(
|
|
41
|
+
* <Ray
|
|
42
|
+
* ref={ray}
|
|
43
|
+
* lineWidth={8}
|
|
44
|
+
* endArrow
|
|
45
|
+
* stroke={'lightseagreen'}
|
|
46
|
+
* fromX={-200}
|
|
47
|
+
* toX={200}
|
|
48
|
+
* />,
|
|
49
|
+
* );
|
|
50
|
+
*
|
|
51
|
+
* yield* ray().start(1, 1);
|
|
52
|
+
* yield* ray().start(0).end(0).start(1, 1);
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
@nodeName('Ray')
|
|
57
|
+
export class Ray extends Curve {
|
|
58
|
+
/**
|
|
59
|
+
* The starting point of the ray.
|
|
60
|
+
*/
|
|
61
|
+
@vector2Signal('from')
|
|
62
|
+
declare public readonly from: Vector2Signal<this>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The ending point of the ray.
|
|
66
|
+
*/
|
|
67
|
+
@vector2Signal('to')
|
|
68
|
+
declare public readonly to: Vector2Signal<this>;
|
|
69
|
+
|
|
70
|
+
public constructor(props: RayProps) {
|
|
71
|
+
super(props);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected override childrenBBox() {
|
|
75
|
+
return BBox.fromPoints(this.from(), this.to());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public override profile(): CurveProfile {
|
|
79
|
+
const segment = new LineSegment(this.from(), this.to());
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
arcLength: segment.arcLength,
|
|
83
|
+
minSin: 1,
|
|
84
|
+
segments: [segment],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public override drawOverlay(
|
|
89
|
+
context: CanvasRenderingContext2D,
|
|
90
|
+
matrix: DOMMatrix,
|
|
91
|
+
) {
|
|
92
|
+
const box = this.childrenBBox().transformCorners(matrix);
|
|
93
|
+
const size = this.computedSize();
|
|
94
|
+
const offset = size.mul(this.anchor()).scale(0.5).transformAsPoint(matrix);
|
|
95
|
+
const from = this.from().transformAsPoint(matrix);
|
|
96
|
+
const to = this.to().transformAsPoint(matrix);
|
|
97
|
+
|
|
98
|
+
context.fillStyle = 'white';
|
|
99
|
+
context.strokeStyle = 'black';
|
|
100
|
+
context.lineWidth = 1;
|
|
101
|
+
|
|
102
|
+
context.beginPath();
|
|
103
|
+
arc(context, from, 4);
|
|
104
|
+
context.fill();
|
|
105
|
+
context.stroke();
|
|
106
|
+
|
|
107
|
+
context.beginPath();
|
|
108
|
+
arc(context, to, 4);
|
|
109
|
+
context.fill();
|
|
110
|
+
context.stroke();
|
|
111
|
+
|
|
112
|
+
context.strokeStyle = 'white';
|
|
113
|
+
context.beginPath();
|
|
114
|
+
drawLine(context, [from, to]);
|
|
115
|
+
context.stroke();
|
|
116
|
+
|
|
117
|
+
context.beginPath();
|
|
118
|
+
drawPivot(context, offset);
|
|
119
|
+
context.stroke();
|
|
120
|
+
|
|
121
|
+
context.beginPath();
|
|
122
|
+
drawLine(context, box);
|
|
123
|
+
context.closePath();
|
|
124
|
+
context.stroke();
|
|
125
|
+
}
|
|
126
|
+
}
|