@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,1633 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT,
|
|
3
|
+
InterpolationFunction,
|
|
4
|
+
PossibleVector2,
|
|
5
|
+
Signal,
|
|
6
|
+
SignalContext,
|
|
7
|
+
SignalExtensions,
|
|
8
|
+
SignalGenerator,
|
|
9
|
+
SignalValue,
|
|
10
|
+
SimpleVector2Signal,
|
|
11
|
+
ThreadGenerator,
|
|
12
|
+
TimingFunction,
|
|
13
|
+
Vector2,
|
|
14
|
+
Vector2Signal,
|
|
15
|
+
Vector2SignalContext,
|
|
16
|
+
deepLerp,
|
|
17
|
+
unwrap,
|
|
18
|
+
} from '@canvas-commons/core';
|
|
19
|
+
import {Node} from '../components/Node';
|
|
20
|
+
import {makeSignalExtensions} from '../utils/makeSignalExtensions';
|
|
21
|
+
import {compound} from './compound';
|
|
22
|
+
import {addInitializer} from './initializers';
|
|
23
|
+
import {getPropertyMetaOrCreate, wrapper} from './signal';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Utility class for handling coordinate space transformations.
|
|
27
|
+
*
|
|
28
|
+
* Provides shared logic for converting between different coordinate spaces:
|
|
29
|
+
* - **Local**: The node's own coordinate system relative to its parent
|
|
30
|
+
* - **Absolute**: The global coordinate system of the scene
|
|
31
|
+
* - **View**: The coordinate system of the view/camera
|
|
32
|
+
* - **Relative**: Coordinate system relative to another specific node
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* // Convert an absolute position to local coordinates
|
|
37
|
+
* const localPos = TransformConverter.absoluteToLocalPosition(node, [100, 200]);
|
|
38
|
+
*
|
|
39
|
+
* // Convert a view space scale to local coordinates
|
|
40
|
+
* const localScale = TransformConverter.viewToLocalScale(node, [2, 2]);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
class TransformConverter {
|
|
44
|
+
private static wrapVectorSignalTransform(
|
|
45
|
+
value: SignalValue<PossibleVector2>,
|
|
46
|
+
transform: (val: Vector2) => Vector2,
|
|
47
|
+
): SignalValue<PossibleVector2> {
|
|
48
|
+
if (typeof value === 'function') {
|
|
49
|
+
return () => transform(new Vector2(value()));
|
|
50
|
+
}
|
|
51
|
+
return transform(new Vector2(value));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static wrapScalarSignalTransform(
|
|
55
|
+
value: SignalValue<number>,
|
|
56
|
+
transform: (val: number) => number,
|
|
57
|
+
): SignalValue<number> {
|
|
58
|
+
if (typeof value === 'function') {
|
|
59
|
+
return () => transform(value());
|
|
60
|
+
}
|
|
61
|
+
return transform(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static absoluteToLocalPosition(
|
|
65
|
+
owner: Node,
|
|
66
|
+
absoluteValue: SignalValue<PossibleVector2>,
|
|
67
|
+
): SignalValue<PossibleVector2> {
|
|
68
|
+
return this.wrapVectorSignalTransform(absoluteValue, val =>
|
|
69
|
+
val.transformAsPoint(owner.worldToParent()),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public static absoluteToLocalScale(
|
|
74
|
+
owner: Node,
|
|
75
|
+
absoluteValue: SignalValue<PossibleVector2>,
|
|
76
|
+
): SignalValue<PossibleVector2> {
|
|
77
|
+
return this.wrapVectorSignalTransform(absoluteValue, val => {
|
|
78
|
+
const parentAbsScale = owner.parent()?.absoluteScale() ?? Vector2.one;
|
|
79
|
+
return val.div(parentAbsScale);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public static absoluteToLocalRotation(
|
|
84
|
+
owner: Node,
|
|
85
|
+
absoluteValue: SignalValue<number>,
|
|
86
|
+
): SignalValue<number> {
|
|
87
|
+
return this.wrapScalarSignalTransform(absoluteValue, val => {
|
|
88
|
+
const parentAbsRotation = owner.parent()?.absoluteRotation() ?? 0;
|
|
89
|
+
return val - parentAbsRotation;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public static relativeToAbsolutePosition(
|
|
94
|
+
targetNode: Node,
|
|
95
|
+
relativeValue: SignalValue<PossibleVector2>,
|
|
96
|
+
): SignalValue<PossibleVector2> {
|
|
97
|
+
return this.wrapVectorSignalTransform(relativeValue, val =>
|
|
98
|
+
val.add(targetNode.absolutePosition()),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static relativeToAbsoluteScale(
|
|
103
|
+
targetNode: Node,
|
|
104
|
+
relativeValue: SignalValue<PossibleVector2>,
|
|
105
|
+
): SignalValue<PossibleVector2> {
|
|
106
|
+
return this.wrapVectorSignalTransform(relativeValue, val =>
|
|
107
|
+
val.mul(targetNode.absoluteScale()),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public static relativeToAbsoluteRotation(
|
|
112
|
+
targetNode: Node,
|
|
113
|
+
relativeValue: SignalValue<number>,
|
|
114
|
+
): SignalValue<number> {
|
|
115
|
+
return this.wrapScalarSignalTransform(
|
|
116
|
+
relativeValue,
|
|
117
|
+
val => val + targetNode.absoluteRotation(),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public static viewToLocalPosition(
|
|
122
|
+
owner: Node,
|
|
123
|
+
viewValue: SignalValue<PossibleVector2>,
|
|
124
|
+
): SignalValue<PossibleVector2> {
|
|
125
|
+
return this.wrapVectorSignalTransform(viewValue, val => {
|
|
126
|
+
const worldPos = val.transformAsPoint(owner.view().localToWorld());
|
|
127
|
+
return worldPos.transformAsPoint(owner.worldToParent());
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public static viewToLocalScale(
|
|
132
|
+
owner: Node,
|
|
133
|
+
viewValue: SignalValue<PossibleVector2>,
|
|
134
|
+
): SignalValue<PossibleVector2> {
|
|
135
|
+
return this.wrapVectorSignalTransform(viewValue, val => {
|
|
136
|
+
const viewMatrix = owner.view().localToWorld();
|
|
137
|
+
const [xMagnitude, yMagnitude] = this.getViewScaleMagnitudes(viewMatrix);
|
|
138
|
+
return new Vector2(val.x / xMagnitude, val.y / yMagnitude);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public static viewToLocalRotation(
|
|
143
|
+
owner: Node,
|
|
144
|
+
viewValue: SignalValue<number>,
|
|
145
|
+
): SignalValue<number> {
|
|
146
|
+
return this.wrapScalarSignalTransform(
|
|
147
|
+
viewValue,
|
|
148
|
+
val => val - this.getViewRotation(owner),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public static getViewRotation(owner: Node): number {
|
|
153
|
+
const viewMatrix = owner.view().localToWorld();
|
|
154
|
+
return Vector2.degrees(viewMatrix.m11, viewMatrix.m12);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public static calculateViewSpaceScale(
|
|
158
|
+
owner: Node,
|
|
159
|
+
localScale: Vector2,
|
|
160
|
+
): Vector2 {
|
|
161
|
+
const viewMatrix = owner.view().localToWorld();
|
|
162
|
+
return new Vector2(
|
|
163
|
+
Vector2.magnitude(
|
|
164
|
+
viewMatrix.m11 * localScale.x,
|
|
165
|
+
viewMatrix.m12 * localScale.x,
|
|
166
|
+
),
|
|
167
|
+
Vector2.magnitude(
|
|
168
|
+
viewMatrix.m21 * localScale.y,
|
|
169
|
+
viewMatrix.m22 * localScale.y,
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private static getViewScaleMagnitudes(
|
|
175
|
+
viewMatrix: DOMMatrix,
|
|
176
|
+
): [number, number] {
|
|
177
|
+
return [
|
|
178
|
+
Vector2.magnitude(viewMatrix.m11, viewMatrix.m12),
|
|
179
|
+
Vector2.magnitude(viewMatrix.m21, viewMatrix.m22),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public static absoluteToLocalLayoutPosition(
|
|
184
|
+
owner: Node,
|
|
185
|
+
absoluteValue: SignalValue<PossibleVector2>,
|
|
186
|
+
): SignalValue<PossibleVector2> {
|
|
187
|
+
return this.wrapVectorSignalTransform(absoluteValue, val =>
|
|
188
|
+
val.transformAsPoint(owner.worldToLocal()),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
interface ComponentTransformMethod<TOwner> {
|
|
194
|
+
(): number;
|
|
195
|
+
(value: SignalValue<number>): TOwner;
|
|
196
|
+
(
|
|
197
|
+
value: SignalValue<number>,
|
|
198
|
+
duration: number,
|
|
199
|
+
timingFunction?: TimingFunction,
|
|
200
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
201
|
+
): ThreadGenerator;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const GETTER_ARGS = 0;
|
|
205
|
+
const SETTER_ARGS = 1;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Enhanced transform method interface that provides both vector and component access.
|
|
209
|
+
*
|
|
210
|
+
* This interface allows you to work with 2D transforms in multiple ways:
|
|
211
|
+
* - Get/set the entire vector: `position()` or `position([x, y])`
|
|
212
|
+
* - Animate the vector: `position([x, y], duration)`
|
|
213
|
+
* - Access individual components: `position.x()` or `position.y()`
|
|
214
|
+
* - Animate components: `position.x(value, duration)`
|
|
215
|
+
*
|
|
216
|
+
* @typeParam TOwner - The type of the object that owns this transform method
|
|
217
|
+
*/
|
|
218
|
+
interface EnhancedTransformMethod<TOwner> {
|
|
219
|
+
/** Get the current transform value */
|
|
220
|
+
(): Vector2;
|
|
221
|
+
/** Set the transform value immediately */
|
|
222
|
+
(value: SignalValue<PossibleVector2>): TOwner;
|
|
223
|
+
/** Animate the transform to a new value */
|
|
224
|
+
(
|
|
225
|
+
value: SignalValue<PossibleVector2>,
|
|
226
|
+
duration: number,
|
|
227
|
+
timingFunction?: TimingFunction,
|
|
228
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
229
|
+
): SignalGenerator<PossibleVector2, Vector2>;
|
|
230
|
+
/** Access to the X component of the transform */
|
|
231
|
+
x: ComponentTransformMethod<TOwner>;
|
|
232
|
+
/** Access to the Y component of the transform */
|
|
233
|
+
y: ComponentTransformMethod<TOwner>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Node-based transform method interface (takes a node parameter)
|
|
237
|
+
interface NodeTransformMethod<TOwner extends Node> {
|
|
238
|
+
(node: Node): CurriedRelativeTransformSignal<TOwner>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Transform-specific signal helpers with enhanced component-wise support
|
|
242
|
+
interface PositionSignalHelpers<TOwner extends Node> {
|
|
243
|
+
abs: EnhancedTransformMethod<TOwner>;
|
|
244
|
+
relativeTo: NodeTransformMethod<TOwner>;
|
|
245
|
+
view: EnhancedTransformMethod<TOwner>;
|
|
246
|
+
local: EnhancedTransformMethod<TOwner>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
interface ScaleSignalHelpers<TOwner extends Node> {
|
|
250
|
+
abs: EnhancedTransformMethod<TOwner>;
|
|
251
|
+
relativeTo: NodeScaleTransformMethod<TOwner>;
|
|
252
|
+
view: EnhancedTransformMethod<TOwner>;
|
|
253
|
+
local: EnhancedTransformMethod<TOwner>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Node-based scale transform method interface (similar to position but returns scale signal)
|
|
257
|
+
interface NodeScaleTransformMethod<TOwner extends Node> {
|
|
258
|
+
(node: Node): CurriedRelativeScaleSignal<TOwner>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Rotation-specific transform method interface (scalar)
|
|
262
|
+
interface EnhancedRotationMethod<TOwner extends Node> {
|
|
263
|
+
(): number;
|
|
264
|
+
(value: SignalValue<number>): TOwner;
|
|
265
|
+
(
|
|
266
|
+
value: SignalValue<number>,
|
|
267
|
+
duration: number,
|
|
268
|
+
timingFunction?: TimingFunction,
|
|
269
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
270
|
+
): SignalGenerator<number, number>;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Node-based rotation transform method interface
|
|
274
|
+
interface NodeRotationMethod<TOwner extends Node> {
|
|
275
|
+
(node: Node): CurriedRelativeRotationSignal<TOwner>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
interface RotationSignalHelpers<TOwner extends Node> {
|
|
279
|
+
abs: EnhancedRotationMethod<TOwner>;
|
|
280
|
+
relativeTo: NodeRotationMethod<TOwner>;
|
|
281
|
+
view: EnhancedRotationMethod<TOwner>;
|
|
282
|
+
local: EnhancedRotationMethod<TOwner>;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Enhanced position signal that provides coordinate space transformation methods.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* // Get/set position in different coordinate spaces
|
|
291
|
+
* node.position([100, 200]); // Set local position
|
|
292
|
+
* node.position.abs([300, 400]); // Set absolute position
|
|
293
|
+
* node.position.relativeTo(other, [50, 0]); // Set position relative to another node
|
|
294
|
+
* node.position.view([0, 0]); // Set position in view space
|
|
295
|
+
*
|
|
296
|
+
* // Component access
|
|
297
|
+
* node.position.x(150); // Set only X coordinate
|
|
298
|
+
* node.position.abs.y(250); // Set only absolute Y coordinate
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
export type PositionSignal<TOwner extends Node = Node> = Vector2Signal<TOwner> &
|
|
302
|
+
PositionSignalHelpers<TOwner>;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Enhanced scale signal that provides coordinate space transformation methods.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* // Scale operations in different coordinate spaces
|
|
310
|
+
* node.scale([2, 1.5]); // Set local scale
|
|
311
|
+
* node.scale.abs([4, 3]); // Set absolute scale
|
|
312
|
+
* node.scale.relativeTo(parent, [0.5, 0.5]); // Scale relative to parent
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
export type ScaleSignal<TOwner extends Node = Node> = Vector2Signal<TOwner> &
|
|
316
|
+
ScaleSignalHelpers<TOwner>;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Enhanced rotation signal that provides coordinate space transformation methods.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```typescript
|
|
323
|
+
* // Rotation operations in different coordinate spaces
|
|
324
|
+
* node.rotation(45); // Set local rotation (degrees)
|
|
325
|
+
* node.rotation.abs(90); // Set absolute rotation
|
|
326
|
+
* node.rotation.relativeTo(other, 180); // Set rotation relative to another node
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
export type RotationSignal<TOwner extends Node = Node> = Signal<
|
|
330
|
+
number,
|
|
331
|
+
number,
|
|
332
|
+
TOwner
|
|
333
|
+
> &
|
|
334
|
+
RotationSignalHelpers<TOwner>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Layout position signal for computed position properties like `top`, `left`, etc.
|
|
338
|
+
*
|
|
339
|
+
* These signals compute their values dynamically based on the node's size and origin,
|
|
340
|
+
* and delegate setting operations to the main position signal.
|
|
341
|
+
*/
|
|
342
|
+
export type LayoutPositionSignal<TOwner extends Node = Node> =
|
|
343
|
+
SimpleVector2Signal<TOwner> & PositionSignalHelpers<TOwner>;
|
|
344
|
+
|
|
345
|
+
// Shared helper functions for creating enhanced transform methods
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* UNIFIED TRANSFORM SIGNAL ARCHITECTURE
|
|
349
|
+
*
|
|
350
|
+
* Every signal in the system is a coordinate space transformation:
|
|
351
|
+
* - Forward transform: base space → target space
|
|
352
|
+
* - Inverse transform: target space → base space
|
|
353
|
+
* - Component access and tweening built on top
|
|
354
|
+
*/
|
|
355
|
+
|
|
356
|
+
// Type for component configuration
|
|
357
|
+
interface ComponentConfig<TTarget> {
|
|
358
|
+
getComponent: (target: TTarget, component: 'x' | 'y') => number;
|
|
359
|
+
setComponent: (
|
|
360
|
+
target: TTarget,
|
|
361
|
+
component: 'x' | 'y',
|
|
362
|
+
value: number,
|
|
363
|
+
) => TTarget;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Standard Vector2 component configuration
|
|
367
|
+
const VECTOR2_COMPONENT_CONFIG: ComponentConfig<Vector2> = {
|
|
368
|
+
getComponent: (vec: Vector2, component: 'x' | 'y') => vec[component],
|
|
369
|
+
setComponent: (vec: Vector2, component: 'x' | 'y', value: number) =>
|
|
370
|
+
new Vector2(component === 'x' ? [value, vec.y] : [vec.x, value]),
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Creates a unified transform signal that handles coordinate space transformations.
|
|
375
|
+
* This is the foundation for all signals (abs, view, local, relativeTo, origin signals).
|
|
376
|
+
*/
|
|
377
|
+
function createTransformSignal<TBase, TTarget, TOwner extends Node>(
|
|
378
|
+
baseGetter: () => TBase,
|
|
379
|
+
baseSetter: (value: SignalValue<TBase>) => TOwner,
|
|
380
|
+
baseTweener: (
|
|
381
|
+
value: SignalValue<TBase>,
|
|
382
|
+
duration: number,
|
|
383
|
+
timingFunction?: TimingFunction,
|
|
384
|
+
interpolationFunction?: InterpolationFunction<TTarget>,
|
|
385
|
+
) => any,
|
|
386
|
+
forwardTransform: (base: TBase) => TTarget,
|
|
387
|
+
inverseTransform: (target: TTarget) => SignalValue<TBase>,
|
|
388
|
+
componentConfig?: ComponentConfig<TTarget>,
|
|
389
|
+
) {
|
|
390
|
+
// Main signal function with proper overloads
|
|
391
|
+
function signal(): TTarget;
|
|
392
|
+
function signal(value: SignalValue<TTarget>): TOwner;
|
|
393
|
+
function signal(
|
|
394
|
+
value: SignalValue<TTarget>,
|
|
395
|
+
duration: number,
|
|
396
|
+
timingFunction?: TimingFunction,
|
|
397
|
+
interpolationFunction?: InterpolationFunction<TTarget>,
|
|
398
|
+
): any;
|
|
399
|
+
function signal(
|
|
400
|
+
value?: SignalValue<TTarget>,
|
|
401
|
+
duration?: number,
|
|
402
|
+
timingFunction?: TimingFunction,
|
|
403
|
+
interpolationFunction?: InterpolationFunction<TTarget>,
|
|
404
|
+
): TTarget | TOwner | any {
|
|
405
|
+
if (arguments.length === 0) {
|
|
406
|
+
// Getter: apply forward transform
|
|
407
|
+
return forwardTransform(baseGetter());
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (arguments.length === 1) {
|
|
411
|
+
// Setter: apply inverse transform and store
|
|
412
|
+
const baseValue = inverseTransform(unwrap(value!));
|
|
413
|
+
return baseSetter(baseValue);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Tweener: apply inverse transform and animate
|
|
417
|
+
const baseValue = inverseTransform(unwrap(value!));
|
|
418
|
+
return baseTweener(
|
|
419
|
+
baseValue,
|
|
420
|
+
duration!,
|
|
421
|
+
timingFunction,
|
|
422
|
+
interpolationFunction,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Add component accessors for Vector2-like targets
|
|
427
|
+
if (componentConfig) {
|
|
428
|
+
const {getComponent, setComponent} = componentConfig;
|
|
429
|
+
|
|
430
|
+
(signal as any).x = function (
|
|
431
|
+
value?: SignalValue<number>,
|
|
432
|
+
duration?: number,
|
|
433
|
+
timingFunction?: TimingFunction,
|
|
434
|
+
interpolationFunction?: InterpolationFunction<TTarget>,
|
|
435
|
+
): number | TOwner | any {
|
|
436
|
+
if (arguments.length === 0) {
|
|
437
|
+
return getComponent(signal(), 'x');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const currentTarget = signal();
|
|
441
|
+
const newTarget = setComponent(currentTarget, 'x', unwrap(value!));
|
|
442
|
+
|
|
443
|
+
if (arguments.length === 1) {
|
|
444
|
+
return signal(newTarget);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Component tweening
|
|
448
|
+
return signal(
|
|
449
|
+
newTarget,
|
|
450
|
+
duration!,
|
|
451
|
+
timingFunction,
|
|
452
|
+
interpolationFunction as InterpolationFunction<TTarget>,
|
|
453
|
+
);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
(signal as any).y = function (
|
|
457
|
+
value?: SignalValue<number>,
|
|
458
|
+
duration?: number,
|
|
459
|
+
timingFunction?: TimingFunction,
|
|
460
|
+
interpolationFunction?: InterpolationFunction<TTarget>,
|
|
461
|
+
): number | TOwner | any {
|
|
462
|
+
if (arguments.length === 0) {
|
|
463
|
+
return getComponent(signal(), 'y');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const currentTarget = signal();
|
|
467
|
+
const newTarget = setComponent(currentTarget, 'y', unwrap(value!));
|
|
468
|
+
|
|
469
|
+
if (arguments.length === 1) {
|
|
470
|
+
return signal(newTarget);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Component tweening
|
|
474
|
+
return signal(
|
|
475
|
+
newTarget,
|
|
476
|
+
duration!,
|
|
477
|
+
timingFunction,
|
|
478
|
+
interpolationFunction as InterpolationFunction<TTarget>,
|
|
479
|
+
);
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return signal;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Create a curried relative transform signal.
|
|
488
|
+
*/
|
|
489
|
+
function createCurriedRelativeSignal<TOwner extends Node = Node>(
|
|
490
|
+
baseContext: PositionSignalContext<TOwner>,
|
|
491
|
+
targetNode: Node,
|
|
492
|
+
): CurriedRelativeTransformSignal<TOwner> {
|
|
493
|
+
// Create the main function
|
|
494
|
+
const curriedSignal = function (
|
|
495
|
+
...args: any[]
|
|
496
|
+
): Vector2 | TOwner | SignalGenerator<PossibleVector2, Vector2> {
|
|
497
|
+
if (args.length === 0) {
|
|
498
|
+
// Getter: compute relative position using internal method
|
|
499
|
+
return baseContext['relativeToImpl'](targetNode);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Setter/Tweener: delegate to internal method
|
|
503
|
+
const [value, duration, timingFunction, interpolationFunction] = args;
|
|
504
|
+
return baseContext['relativeToImpl'](
|
|
505
|
+
targetNode,
|
|
506
|
+
value,
|
|
507
|
+
duration,
|
|
508
|
+
timingFunction,
|
|
509
|
+
interpolationFunction,
|
|
510
|
+
);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// Add component accessors
|
|
514
|
+
Object.defineProperty(curriedSignal, 'x', {
|
|
515
|
+
get() {
|
|
516
|
+
return function (...args: any[]): number | TOwner | ThreadGenerator {
|
|
517
|
+
if (args.length === 0) {
|
|
518
|
+
return (curriedSignal() as Vector2).x;
|
|
519
|
+
}
|
|
520
|
+
// Component setter/tweener - build new vector and set/animate it
|
|
521
|
+
const current = curriedSignal() as Vector2;
|
|
522
|
+
if (args.length === 1) {
|
|
523
|
+
return curriedSignal(
|
|
524
|
+
new Vector2([unwrap(args[0]), current.y]),
|
|
525
|
+
) as TOwner;
|
|
526
|
+
}
|
|
527
|
+
// Component tweener
|
|
528
|
+
const [value, duration, timingFunction, interpolationFunction] = args;
|
|
529
|
+
return curriedSignal(
|
|
530
|
+
new Vector2([unwrap(value), current.y]),
|
|
531
|
+
duration,
|
|
532
|
+
timingFunction,
|
|
533
|
+
interpolationFunction as any,
|
|
534
|
+
) as ThreadGenerator;
|
|
535
|
+
};
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
Object.defineProperty(curriedSignal, 'y', {
|
|
540
|
+
get() {
|
|
541
|
+
return function (...args: any[]): number | TOwner | ThreadGenerator {
|
|
542
|
+
if (args.length === 0) {
|
|
543
|
+
return (curriedSignal() as Vector2).y;
|
|
544
|
+
}
|
|
545
|
+
// Component setter/tweener - build new vector and set/animate it
|
|
546
|
+
const current = curriedSignal() as Vector2;
|
|
547
|
+
if (args.length === 1) {
|
|
548
|
+
return curriedSignal(
|
|
549
|
+
new Vector2([current.x, unwrap(args[0])]),
|
|
550
|
+
) as TOwner;
|
|
551
|
+
}
|
|
552
|
+
// Component tweener
|
|
553
|
+
const [value, duration, timingFunction, interpolationFunction] = args;
|
|
554
|
+
return curriedSignal(
|
|
555
|
+
new Vector2([current.x, unwrap(value)]),
|
|
556
|
+
duration,
|
|
557
|
+
timingFunction,
|
|
558
|
+
interpolationFunction as any,
|
|
559
|
+
) as ThreadGenerator;
|
|
560
|
+
};
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
return curriedSignal as CurriedRelativeTransformSignal<TOwner>;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Properly typed curried signal interface for position
|
|
568
|
+
interface CurriedRelativeTransformSignal<TOwner extends Node> {
|
|
569
|
+
(): Vector2;
|
|
570
|
+
(value: SignalValue<PossibleVector2>): TOwner;
|
|
571
|
+
(
|
|
572
|
+
value: SignalValue<PossibleVector2>,
|
|
573
|
+
duration: number,
|
|
574
|
+
timingFunction?: TimingFunction,
|
|
575
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
576
|
+
): SignalGenerator<PossibleVector2, Vector2>;
|
|
577
|
+
|
|
578
|
+
x: ComponentTransformMethod<TOwner>;
|
|
579
|
+
y: ComponentTransformMethod<TOwner>;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Create a curried relative transform signal for rotation.
|
|
584
|
+
*/
|
|
585
|
+
function createCurriedRelativeRotationSignal<TOwner extends Node = Node>(
|
|
586
|
+
baseContext: RotationSignalContext<TOwner>,
|
|
587
|
+
targetNode: Node,
|
|
588
|
+
): CurriedRelativeRotationSignal<TOwner> {
|
|
589
|
+
// Create the main function
|
|
590
|
+
const curriedSignal = function (
|
|
591
|
+
...args: any[]
|
|
592
|
+
): number | TOwner | SignalGenerator<number, number> {
|
|
593
|
+
if (args.length === 0) {
|
|
594
|
+
// Getter: compute relative rotation using internal method
|
|
595
|
+
return baseContext['relativeToImpl'](targetNode);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Setter/Tweener: delegate to internal method
|
|
599
|
+
const [value, duration, timingFunction, interpolationFunction] = args;
|
|
600
|
+
return baseContext['relativeToImpl'](
|
|
601
|
+
targetNode,
|
|
602
|
+
value,
|
|
603
|
+
duration,
|
|
604
|
+
timingFunction,
|
|
605
|
+
interpolationFunction,
|
|
606
|
+
);
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
return curriedSignal as CurriedRelativeRotationSignal<TOwner>;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Create a curried relative transform signal for scale.
|
|
614
|
+
*/
|
|
615
|
+
function createCurriedRelativeScaleSignal<TOwner extends Node = Node>(
|
|
616
|
+
baseContext: ScaleSignalContext<TOwner>,
|
|
617
|
+
targetNode: Node,
|
|
618
|
+
): CurriedRelativeScaleSignal<TOwner> {
|
|
619
|
+
// Create the main function
|
|
620
|
+
const curriedSignal = function (
|
|
621
|
+
...args: any[]
|
|
622
|
+
): Vector2 | TOwner | SignalGenerator<PossibleVector2, Vector2> {
|
|
623
|
+
if (args.length === 0) {
|
|
624
|
+
// Getter: compute relative scale
|
|
625
|
+
const absScale = (baseContext as any).owner.absoluteScale();
|
|
626
|
+
const targetAbsScale = targetNode.absoluteScale();
|
|
627
|
+
return absScale.div(targetAbsScale);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Setter/Tweener: convert relative scale to local scale and set it
|
|
631
|
+
const [value, duration, timingFunction, interpolationFunction] = args;
|
|
632
|
+
const absoluteValue = TransformConverter.relativeToAbsoluteScale(
|
|
633
|
+
targetNode,
|
|
634
|
+
value,
|
|
635
|
+
);
|
|
636
|
+
const localValue = TransformConverter.absoluteToLocalScale(
|
|
637
|
+
(baseContext as any).owner,
|
|
638
|
+
absoluteValue,
|
|
639
|
+
);
|
|
640
|
+
return (baseContext as any).invoke(
|
|
641
|
+
localValue,
|
|
642
|
+
duration,
|
|
643
|
+
timingFunction,
|
|
644
|
+
interpolationFunction,
|
|
645
|
+
) as TOwner | SignalGenerator<PossibleVector2, Vector2>;
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
// Add component accessors
|
|
649
|
+
Object.defineProperty(curriedSignal, 'x', {
|
|
650
|
+
get() {
|
|
651
|
+
return function (...args: any[]): number | TOwner | ThreadGenerator {
|
|
652
|
+
if (args.length === 0) {
|
|
653
|
+
return (curriedSignal() as Vector2).x;
|
|
654
|
+
}
|
|
655
|
+
// Component setter/tweener - build new vector and set/animate it
|
|
656
|
+
const current = curriedSignal() as Vector2;
|
|
657
|
+
if (args.length === 1) {
|
|
658
|
+
return curriedSignal(
|
|
659
|
+
new Vector2([unwrap(args[0]), current.y]),
|
|
660
|
+
) as TOwner;
|
|
661
|
+
}
|
|
662
|
+
// Component tweener
|
|
663
|
+
const [value, duration, timingFunction, interpolationFunction] = args;
|
|
664
|
+
return curriedSignal(
|
|
665
|
+
new Vector2([unwrap(value), current.y]),
|
|
666
|
+
duration,
|
|
667
|
+
timingFunction,
|
|
668
|
+
interpolationFunction as any,
|
|
669
|
+
) as ThreadGenerator;
|
|
670
|
+
};
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
Object.defineProperty(curriedSignal, 'y', {
|
|
675
|
+
get() {
|
|
676
|
+
return function (...args: any[]): number | TOwner | ThreadGenerator {
|
|
677
|
+
if (args.length === 0) {
|
|
678
|
+
return (curriedSignal() as Vector2).y;
|
|
679
|
+
}
|
|
680
|
+
// Component setter/tweener - build new vector and set/animate it
|
|
681
|
+
const current = curriedSignal() as Vector2;
|
|
682
|
+
if (args.length === 1) {
|
|
683
|
+
return curriedSignal(
|
|
684
|
+
new Vector2([current.x, unwrap(args[0])]),
|
|
685
|
+
) as TOwner;
|
|
686
|
+
}
|
|
687
|
+
// Component tweener
|
|
688
|
+
const [value, duration, timingFunction, interpolationFunction] = args;
|
|
689
|
+
return curriedSignal(
|
|
690
|
+
new Vector2([current.x, unwrap(value)]),
|
|
691
|
+
duration,
|
|
692
|
+
timingFunction,
|
|
693
|
+
interpolationFunction as any,
|
|
694
|
+
) as ThreadGenerator;
|
|
695
|
+
};
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return curriedSignal as CurriedRelativeScaleSignal<TOwner>;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Properly typed curried signal interface for rotation
|
|
703
|
+
interface CurriedRelativeRotationSignal<TOwner extends Node> {
|
|
704
|
+
(): number;
|
|
705
|
+
(value: SignalValue<number>): TOwner;
|
|
706
|
+
(
|
|
707
|
+
value: SignalValue<number>,
|
|
708
|
+
duration: number,
|
|
709
|
+
timingFunction?: TimingFunction,
|
|
710
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
711
|
+
): SignalGenerator<number, number>;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Properly typed curried signal interface for scale
|
|
715
|
+
interface CurriedRelativeScaleSignal<TOwner extends Node> {
|
|
716
|
+
(): Vector2;
|
|
717
|
+
(value: SignalValue<PossibleVector2>): TOwner;
|
|
718
|
+
(
|
|
719
|
+
value: SignalValue<PossibleVector2>,
|
|
720
|
+
duration: number,
|
|
721
|
+
timingFunction?: TimingFunction,
|
|
722
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
723
|
+
): SignalGenerator<PossibleVector2, Vector2>;
|
|
724
|
+
|
|
725
|
+
x: ComponentTransformMethod<TOwner>;
|
|
726
|
+
y: ComponentTransformMethod<TOwner>;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Position-aware Vector2 signal context
|
|
730
|
+
export class PositionSignalContext<
|
|
731
|
+
TOwner extends Node = Node,
|
|
732
|
+
> extends Vector2SignalContext<TOwner> {
|
|
733
|
+
public constructor(
|
|
734
|
+
entries: ('x' | 'y' | [keyof Vector2, Signal<number, number, TOwner>])[],
|
|
735
|
+
parser: (value: PossibleVector2) => Vector2,
|
|
736
|
+
initial: SignalValue<PossibleVector2>,
|
|
737
|
+
interpolation: InterpolationFunction<Vector2>,
|
|
738
|
+
owner: TOwner,
|
|
739
|
+
extensions: Partial<SignalExtensions<PossibleVector2, Vector2>> = {},
|
|
740
|
+
) {
|
|
741
|
+
super(entries, parser, initial, interpolation, owner, extensions);
|
|
742
|
+
|
|
743
|
+
// Create enhanced transform methods using unified approach
|
|
744
|
+
Object.defineProperty(this.invokable, 'abs', {
|
|
745
|
+
value: createTransformSignal<PossibleVector2, Vector2, TOwner>(
|
|
746
|
+
() => this.get(),
|
|
747
|
+
value => this.invoke(value) as TOwner,
|
|
748
|
+
(value, duration, timingFunction, interpolationFunction) =>
|
|
749
|
+
this.invoke(value, duration, timingFunction, interpolationFunction),
|
|
750
|
+
local =>
|
|
751
|
+
new Vector2(local).transformAsPoint(this.owner.parentToWorld()),
|
|
752
|
+
absolute =>
|
|
753
|
+
TransformConverter.absoluteToLocalPosition(
|
|
754
|
+
this.owner,
|
|
755
|
+
unwrap(absolute!) as PossibleVector2,
|
|
756
|
+
),
|
|
757
|
+
VECTOR2_COMPONENT_CONFIG,
|
|
758
|
+
),
|
|
759
|
+
enumerable: false,
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
Object.defineProperty(this.invokable, 'view', {
|
|
763
|
+
value: createTransformSignal<PossibleVector2, Vector2, TOwner>(
|
|
764
|
+
() => this.get(),
|
|
765
|
+
value => this.invoke(value) as TOwner,
|
|
766
|
+
(value, duration, timingFunction, interpolationFunction) =>
|
|
767
|
+
this.invoke(value, duration, timingFunction, interpolationFunction),
|
|
768
|
+
local => {
|
|
769
|
+
// Transform local position to world, then world to view space
|
|
770
|
+
const worldPos = new Vector2(local).transformAsPoint(
|
|
771
|
+
this.owner.parentToWorld(),
|
|
772
|
+
);
|
|
773
|
+
return worldPos.transformAsPoint(this.owner.view().worldToLocal());
|
|
774
|
+
},
|
|
775
|
+
view =>
|
|
776
|
+
TransformConverter.viewToLocalPosition(
|
|
777
|
+
this.owner,
|
|
778
|
+
unwrap(view!) as PossibleVector2,
|
|
779
|
+
),
|
|
780
|
+
VECTOR2_COMPONENT_CONFIG,
|
|
781
|
+
),
|
|
782
|
+
enumerable: false,
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
Object.defineProperty(this.invokable, 'local', {
|
|
786
|
+
value: createTransformSignal<PossibleVector2, Vector2, TOwner>(
|
|
787
|
+
() => this.get(),
|
|
788
|
+
value => this.invoke(value) as TOwner,
|
|
789
|
+
(value, duration, timingFunction, interpolationFunction) =>
|
|
790
|
+
this.invoke(value, duration, timingFunction, interpolationFunction),
|
|
791
|
+
local => new Vector2(local),
|
|
792
|
+
local => local,
|
|
793
|
+
VECTOR2_COMPONENT_CONFIG,
|
|
794
|
+
),
|
|
795
|
+
enumerable: false,
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
Object.defineProperty(this.invokable, 'relativeTo', {
|
|
799
|
+
value: this.relativeTo.bind(this),
|
|
800
|
+
enumerable: false,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
public override toSignal(): PositionSignal<TOwner> {
|
|
805
|
+
return this.invokable as PositionSignal<TOwner>;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Internal method for the actual relativeTo implementation
|
|
809
|
+
private relativeToImpl(
|
|
810
|
+
node: Node,
|
|
811
|
+
value?: SignalValue<PossibleVector2>,
|
|
812
|
+
duration?: number,
|
|
813
|
+
timingFunction?: TimingFunction,
|
|
814
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
815
|
+
): Vector2 | TOwner | SignalGenerator<PossibleVector2, Vector2> {
|
|
816
|
+
if (arguments.length === 1) {
|
|
817
|
+
const absPosition = this.owner.absolutePosition();
|
|
818
|
+
const targetAbsPosition = node.absolutePosition();
|
|
819
|
+
return absPosition.sub(targetAbsPosition);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Convert relative value to local value and set it
|
|
823
|
+
const absoluteValue = TransformConverter.relativeToAbsolutePosition(
|
|
824
|
+
node,
|
|
825
|
+
value!,
|
|
826
|
+
);
|
|
827
|
+
const localValue = TransformConverter.absoluteToLocalPosition(
|
|
828
|
+
this.owner,
|
|
829
|
+
absoluteValue,
|
|
830
|
+
);
|
|
831
|
+
return this.invoke(
|
|
832
|
+
localValue,
|
|
833
|
+
duration,
|
|
834
|
+
timingFunction,
|
|
835
|
+
interpolationFunction,
|
|
836
|
+
) as TOwner | SignalGenerator<PossibleVector2, Vector2>;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
public relativeTo(node: Node): CurriedRelativeTransformSignal<TOwner> {
|
|
840
|
+
return createCurriedRelativeSignal(this, node);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Scale-aware Vector2 signal context
|
|
845
|
+
export class ScaleSignalContext<
|
|
846
|
+
TOwner extends Node = Node,
|
|
847
|
+
> extends Vector2SignalContext<TOwner> {
|
|
848
|
+
public constructor(
|
|
849
|
+
entries: ('x' | 'y' | [keyof Vector2, Signal<number, number, TOwner>])[],
|
|
850
|
+
parser: (value: PossibleVector2) => Vector2,
|
|
851
|
+
initial: SignalValue<PossibleVector2>,
|
|
852
|
+
interpolation: InterpolationFunction<Vector2>,
|
|
853
|
+
owner: TOwner,
|
|
854
|
+
extensions: Partial<SignalExtensions<PossibleVector2, Vector2>> = {},
|
|
855
|
+
) {
|
|
856
|
+
super(entries, parser, initial, interpolation, owner, extensions);
|
|
857
|
+
|
|
858
|
+
// Create enhanced transform methods using unified approach
|
|
859
|
+
Object.defineProperty(this.invokable, 'abs', {
|
|
860
|
+
value: createTransformSignal<PossibleVector2, Vector2, TOwner>(
|
|
861
|
+
() => this.get(),
|
|
862
|
+
value => this.invoke(value) as TOwner,
|
|
863
|
+
(value, duration, timingFunction, interpolationFunction) =>
|
|
864
|
+
this.invoke(value, duration, timingFunction, interpolationFunction),
|
|
865
|
+
() => {
|
|
866
|
+
const matrix = this.owner.localToWorld();
|
|
867
|
+
return new Vector2(
|
|
868
|
+
Vector2.magnitude(matrix.m11, matrix.m12),
|
|
869
|
+
Vector2.magnitude(matrix.m21, matrix.m22),
|
|
870
|
+
);
|
|
871
|
+
},
|
|
872
|
+
absolute =>
|
|
873
|
+
TransformConverter.absoluteToLocalScale(
|
|
874
|
+
this.owner,
|
|
875
|
+
unwrap(absolute!) as PossibleVector2,
|
|
876
|
+
),
|
|
877
|
+
VECTOR2_COMPONENT_CONFIG,
|
|
878
|
+
),
|
|
879
|
+
enumerable: false,
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
Object.defineProperty(this.invokable, 'view', {
|
|
883
|
+
value: createTransformSignal<PossibleVector2, Vector2, TOwner>(
|
|
884
|
+
() => this.get(),
|
|
885
|
+
value => this.invoke(value) as TOwner,
|
|
886
|
+
(value, duration, timingFunction, interpolationFunction) =>
|
|
887
|
+
this.invoke(value, duration, timingFunction, interpolationFunction),
|
|
888
|
+
local =>
|
|
889
|
+
TransformConverter.calculateViewSpaceScale(
|
|
890
|
+
this.owner,
|
|
891
|
+
new Vector2(local),
|
|
892
|
+
),
|
|
893
|
+
view =>
|
|
894
|
+
TransformConverter.viewToLocalScale(
|
|
895
|
+
this.owner,
|
|
896
|
+
unwrap(view!) as PossibleVector2,
|
|
897
|
+
),
|
|
898
|
+
VECTOR2_COMPONENT_CONFIG,
|
|
899
|
+
),
|
|
900
|
+
enumerable: false,
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
Object.defineProperty(this.invokable, 'local', {
|
|
904
|
+
value: createTransformSignal<PossibleVector2, Vector2, TOwner>(
|
|
905
|
+
() => this.get(),
|
|
906
|
+
value => this.invoke(value) as TOwner,
|
|
907
|
+
(value, duration, timingFunction, interpolationFunction) =>
|
|
908
|
+
this.invoke(value, duration, timingFunction, interpolationFunction),
|
|
909
|
+
local => new Vector2(local),
|
|
910
|
+
local => local,
|
|
911
|
+
VECTOR2_COMPONENT_CONFIG,
|
|
912
|
+
),
|
|
913
|
+
enumerable: false,
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
Object.defineProperty(this.invokable, 'relativeTo', {
|
|
917
|
+
value: this.relativeTo.bind(this),
|
|
918
|
+
enumerable: false,
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
public override toSignal(): ScaleSignal<TOwner> {
|
|
923
|
+
return this.invokable as ScaleSignal<TOwner>;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
public relativeTo(node: Node): CurriedRelativeScaleSignal<TOwner> {
|
|
927
|
+
return createCurriedRelativeScaleSignal(this, node);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Rotation-aware signal context
|
|
932
|
+
export class RotationSignalContext<
|
|
933
|
+
TOwner extends Node = Node,
|
|
934
|
+
> extends SignalContext<number, number, TOwner> {
|
|
935
|
+
public constructor(
|
|
936
|
+
initial: SignalValue<number> | undefined,
|
|
937
|
+
interpolation: InterpolationFunction<number>,
|
|
938
|
+
owner: TOwner,
|
|
939
|
+
parser: (value: number) => number = value => value,
|
|
940
|
+
extensions: Partial<SignalExtensions<number, number>> = {},
|
|
941
|
+
) {
|
|
942
|
+
super(initial, interpolation, owner, parser, extensions);
|
|
943
|
+
|
|
944
|
+
Object.defineProperty(this.invokable, 'abs', {
|
|
945
|
+
value: this.abs.bind(this),
|
|
946
|
+
enumerable: false,
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
Object.defineProperty(this.invokable, 'relativeTo', {
|
|
950
|
+
value: this.relativeTo.bind(this),
|
|
951
|
+
enumerable: false,
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
Object.defineProperty(this.invokable, 'view', {
|
|
955
|
+
value: this.view.bind(this),
|
|
956
|
+
enumerable: false,
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
Object.defineProperty(this.invokable, 'local', {
|
|
960
|
+
value: this.local.bind(this),
|
|
961
|
+
enumerable: false,
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
public override toSignal(): RotationSignal<TOwner> {
|
|
966
|
+
return this.invokable as RotationSignal<TOwner>;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
public abs(): number;
|
|
970
|
+
public abs(value: SignalValue<number>): TOwner;
|
|
971
|
+
public abs(
|
|
972
|
+
value: SignalValue<number>,
|
|
973
|
+
duration: number,
|
|
974
|
+
timingFunction?: TimingFunction,
|
|
975
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
976
|
+
): SignalGenerator<number, number>;
|
|
977
|
+
public abs(
|
|
978
|
+
value?: SignalValue<number>,
|
|
979
|
+
duration?: number,
|
|
980
|
+
timingFunction?: TimingFunction,
|
|
981
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
982
|
+
): number | TOwner | SignalGenerator<number, number> {
|
|
983
|
+
if (arguments.length === 0) {
|
|
984
|
+
// Get absolute rotation by extracting it from localToWorld matrix
|
|
985
|
+
const matrix = this.owner.localToWorld();
|
|
986
|
+
return Vector2.degrees(matrix.m11, matrix.m12);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Convert absolute rotation to local rotation and set it
|
|
990
|
+
const localValue = TransformConverter.absoluteToLocalRotation(
|
|
991
|
+
this.owner,
|
|
992
|
+
value!,
|
|
993
|
+
);
|
|
994
|
+
return this.invoke(
|
|
995
|
+
localValue,
|
|
996
|
+
duration,
|
|
997
|
+
timingFunction,
|
|
998
|
+
interpolationFunction,
|
|
999
|
+
) as TOwner | SignalGenerator<number, number>;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Internal method for the actual relativeTo implementation
|
|
1003
|
+
private relativeToImpl(
|
|
1004
|
+
node: Node,
|
|
1005
|
+
value?: SignalValue<number>,
|
|
1006
|
+
duration?: number,
|
|
1007
|
+
timingFunction?: TimingFunction,
|
|
1008
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1009
|
+
): number | TOwner | SignalGenerator<number, number> {
|
|
1010
|
+
if (arguments.length === 1) {
|
|
1011
|
+
const absRotation = this.owner.absoluteRotation();
|
|
1012
|
+
const targetAbsRotation = node.absoluteRotation();
|
|
1013
|
+
return absRotation - targetAbsRotation;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Convert relative rotation to local rotation and set it
|
|
1017
|
+
const absoluteValue = TransformConverter.relativeToAbsoluteRotation(
|
|
1018
|
+
node,
|
|
1019
|
+
value!,
|
|
1020
|
+
);
|
|
1021
|
+
const localValue = TransformConverter.absoluteToLocalRotation(
|
|
1022
|
+
this.owner,
|
|
1023
|
+
absoluteValue,
|
|
1024
|
+
);
|
|
1025
|
+
return this.invoke(
|
|
1026
|
+
localValue,
|
|
1027
|
+
duration,
|
|
1028
|
+
timingFunction,
|
|
1029
|
+
interpolationFunction,
|
|
1030
|
+
) as TOwner | SignalGenerator<number, number>;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
public relativeTo(node: Node): CurriedRelativeRotationSignal<TOwner> {
|
|
1034
|
+
return createCurriedRelativeRotationSignal(this, node);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
public view(): number;
|
|
1038
|
+
public view(value: SignalValue<number>): TOwner;
|
|
1039
|
+
public view(
|
|
1040
|
+
value: SignalValue<number>,
|
|
1041
|
+
duration: number,
|
|
1042
|
+
timingFunction?: TimingFunction,
|
|
1043
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1044
|
+
): SignalGenerator<number, number>;
|
|
1045
|
+
public view(
|
|
1046
|
+
value?: SignalValue<number>,
|
|
1047
|
+
duration?: number,
|
|
1048
|
+
timingFunction?: TimingFunction,
|
|
1049
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1050
|
+
): number | TOwner | SignalGenerator<number, number> {
|
|
1051
|
+
if (arguments.length === 0) {
|
|
1052
|
+
// For rotation in view space, we need to get the rotation relative to view
|
|
1053
|
+
const currentRotation = this.get();
|
|
1054
|
+
const viewRotation = TransformConverter.getViewRotation(this.owner);
|
|
1055
|
+
return currentRotation + viewRotation;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Convert view space rotation to local rotation and set it
|
|
1059
|
+
const localValue = TransformConverter.viewToLocalRotation(
|
|
1060
|
+
this.owner,
|
|
1061
|
+
value!,
|
|
1062
|
+
);
|
|
1063
|
+
return this.invoke(
|
|
1064
|
+
localValue,
|
|
1065
|
+
duration,
|
|
1066
|
+
timingFunction,
|
|
1067
|
+
interpolationFunction,
|
|
1068
|
+
) as TOwner | SignalGenerator<number, number>;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
public local(): number;
|
|
1072
|
+
public local(value: SignalValue<number>): TOwner;
|
|
1073
|
+
public local(
|
|
1074
|
+
value: SignalValue<number>,
|
|
1075
|
+
duration: number,
|
|
1076
|
+
timingFunction?: TimingFunction,
|
|
1077
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1078
|
+
): SignalGenerator<number, number>;
|
|
1079
|
+
public local(
|
|
1080
|
+
value?: SignalValue<number>,
|
|
1081
|
+
duration?: number,
|
|
1082
|
+
timingFunction?: TimingFunction,
|
|
1083
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1084
|
+
): number | TOwner | SignalGenerator<number, number> {
|
|
1085
|
+
if (arguments.length === 0) {
|
|
1086
|
+
return this.get();
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Local value is just the raw value
|
|
1090
|
+
return this.invoke(
|
|
1091
|
+
value!,
|
|
1092
|
+
duration,
|
|
1093
|
+
timingFunction,
|
|
1094
|
+
interpolationFunction,
|
|
1095
|
+
) as TOwner | SignalGenerator<number, number>;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Custom setter function type for layout positions
|
|
1100
|
+
type LayoutPositionSetter<TOwner> = (
|
|
1101
|
+
value: SignalValue<PossibleVector2> | typeof DEFAULT,
|
|
1102
|
+
) => TOwner;
|
|
1103
|
+
|
|
1104
|
+
// Layout position signal context (for computed position signals like top, left, etc.)
|
|
1105
|
+
export class LayoutPositionSignalContext<
|
|
1106
|
+
TOwner extends Node = Node,
|
|
1107
|
+
> extends SignalContext<PossibleVector2, Vector2, TOwner> {
|
|
1108
|
+
private readonly hasCustomDelegate: boolean;
|
|
1109
|
+
|
|
1110
|
+
public constructor(
|
|
1111
|
+
initial: SignalValue<PossibleVector2> | undefined,
|
|
1112
|
+
interpolation: InterpolationFunction<Vector2>,
|
|
1113
|
+
owner: TOwner,
|
|
1114
|
+
parser: (value: PossibleVector2) => Vector2 = value => new Vector2(value),
|
|
1115
|
+
extensions: Partial<SignalExtensions<PossibleVector2, Vector2>> = {},
|
|
1116
|
+
) {
|
|
1117
|
+
super(initial, interpolation, owner, parser, extensions);
|
|
1118
|
+
|
|
1119
|
+
// Track if we have custom delegation behavior
|
|
1120
|
+
this.hasCustomDelegate = Boolean(extensions.getter || extensions.setter);
|
|
1121
|
+
|
|
1122
|
+
// Create enhanced transform methods with component access using shared helpers
|
|
1123
|
+
const absMethod = this.abs.bind(this);
|
|
1124
|
+
|
|
1125
|
+
// Add component methods to abs
|
|
1126
|
+
(absMethod as any).x = function (
|
|
1127
|
+
value?: SignalValue<number>,
|
|
1128
|
+
duration?: number,
|
|
1129
|
+
timingFunction?: TimingFunction,
|
|
1130
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1131
|
+
): number | TOwner | ThreadGenerator {
|
|
1132
|
+
if (arguments.length === 0) {
|
|
1133
|
+
return absMethod().x;
|
|
1134
|
+
}
|
|
1135
|
+
// Get the current absolute position once and preserve the y component
|
|
1136
|
+
const currentAbsPos = absMethod();
|
|
1137
|
+
const newAbsPos = new Vector2([unwrap(value!), currentAbsPos.y]);
|
|
1138
|
+
if (arguments.length === 1) {
|
|
1139
|
+
return absMethod(newAbsPos);
|
|
1140
|
+
}
|
|
1141
|
+
return absMethod(
|
|
1142
|
+
newAbsPos,
|
|
1143
|
+
duration!,
|
|
1144
|
+
timingFunction,
|
|
1145
|
+
interpolationFunction as any,
|
|
1146
|
+
) as ThreadGenerator;
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
(absMethod as any).y = function (
|
|
1150
|
+
value?: SignalValue<number>,
|
|
1151
|
+
duration?: number,
|
|
1152
|
+
timingFunction?: TimingFunction,
|
|
1153
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1154
|
+
): number | TOwner | ThreadGenerator {
|
|
1155
|
+
if (arguments.length === 0) {
|
|
1156
|
+
return absMethod().y;
|
|
1157
|
+
}
|
|
1158
|
+
// Get the current absolute position once and preserve the x component
|
|
1159
|
+
const currentAbsPos = absMethod();
|
|
1160
|
+
const newAbsPos = new Vector2([currentAbsPos.x, unwrap(value!)]);
|
|
1161
|
+
if (arguments.length === 1) {
|
|
1162
|
+
return absMethod(newAbsPos);
|
|
1163
|
+
}
|
|
1164
|
+
return absMethod(
|
|
1165
|
+
newAbsPos,
|
|
1166
|
+
duration!,
|
|
1167
|
+
timingFunction,
|
|
1168
|
+
interpolationFunction as any,
|
|
1169
|
+
) as ThreadGenerator;
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
Object.defineProperty(this.invokable, 'abs', {
|
|
1173
|
+
value: absMethod,
|
|
1174
|
+
enumerable: false,
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
Object.defineProperty(this.invokable, 'relativeTo', {
|
|
1178
|
+
value: this.relativeTo.bind(this),
|
|
1179
|
+
enumerable: false,
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
const viewMethod = this.view.bind(this);
|
|
1183
|
+
|
|
1184
|
+
// Add component methods to view
|
|
1185
|
+
(viewMethod as any).x = function (
|
|
1186
|
+
this: LayoutPositionSignalContext<TOwner>,
|
|
1187
|
+
value?: SignalValue<number>,
|
|
1188
|
+
duration?: number,
|
|
1189
|
+
timingFunction?: TimingFunction,
|
|
1190
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1191
|
+
): number | TOwner | ThreadGenerator {
|
|
1192
|
+
if (arguments.length === 0) {
|
|
1193
|
+
return viewMethod().x;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// For view space component setting, we need to be more precise about preserving the other component
|
|
1197
|
+
// Get the current LOCAL position and current VIEW position
|
|
1198
|
+
const currentLocal = this.get();
|
|
1199
|
+
const currentView = currentLocal
|
|
1200
|
+
.transformAsPoint(this.owner.localToWorld())
|
|
1201
|
+
.transformAsPoint(this.owner.view().worldToLocal());
|
|
1202
|
+
|
|
1203
|
+
// Create target view position with only the x component changed
|
|
1204
|
+
const targetView = new Vector2([unwrap(value!), currentView.y]);
|
|
1205
|
+
|
|
1206
|
+
// Transform to local and set
|
|
1207
|
+
const targetLocal = new Vector2(
|
|
1208
|
+
unwrap(TransformConverter.viewToLocalPosition(this.owner, targetView)),
|
|
1209
|
+
);
|
|
1210
|
+
|
|
1211
|
+
if (arguments.length === 1) {
|
|
1212
|
+
return this.invoke(targetLocal) as TOwner;
|
|
1213
|
+
}
|
|
1214
|
+
return this.invoke(
|
|
1215
|
+
targetLocal,
|
|
1216
|
+
duration!,
|
|
1217
|
+
timingFunction,
|
|
1218
|
+
interpolationFunction as any,
|
|
1219
|
+
) as ThreadGenerator;
|
|
1220
|
+
}.bind(this);
|
|
1221
|
+
|
|
1222
|
+
(viewMethod as any).y = function (
|
|
1223
|
+
this: LayoutPositionSignalContext<TOwner>,
|
|
1224
|
+
value?: SignalValue<number>,
|
|
1225
|
+
duration?: number,
|
|
1226
|
+
timingFunction?: TimingFunction,
|
|
1227
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1228
|
+
): number | TOwner | ThreadGenerator {
|
|
1229
|
+
if (arguments.length === 0) {
|
|
1230
|
+
return viewMethod().y;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// Get the current LOCAL position and current VIEW position
|
|
1234
|
+
const currentLocal = this.get();
|
|
1235
|
+
const currentView = currentLocal
|
|
1236
|
+
.transformAsPoint(this.owner.localToWorld())
|
|
1237
|
+
.transformAsPoint(this.owner.view().worldToLocal());
|
|
1238
|
+
|
|
1239
|
+
// Create target view position with only the y component changed
|
|
1240
|
+
const targetView = new Vector2([currentView.x, unwrap(value!)]);
|
|
1241
|
+
|
|
1242
|
+
// Transform to local and set
|
|
1243
|
+
const targetLocal = new Vector2(
|
|
1244
|
+
unwrap(TransformConverter.viewToLocalPosition(this.owner, targetView)),
|
|
1245
|
+
);
|
|
1246
|
+
|
|
1247
|
+
if (arguments.length === 1) {
|
|
1248
|
+
return this.invoke(targetLocal) as TOwner;
|
|
1249
|
+
}
|
|
1250
|
+
return this.invoke(
|
|
1251
|
+
targetLocal,
|
|
1252
|
+
duration!,
|
|
1253
|
+
timingFunction,
|
|
1254
|
+
interpolationFunction as any,
|
|
1255
|
+
) as ThreadGenerator;
|
|
1256
|
+
}.bind(this);
|
|
1257
|
+
|
|
1258
|
+
Object.defineProperty(this.invokable, 'view', {
|
|
1259
|
+
value: viewMethod,
|
|
1260
|
+
enumerable: false,
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
const localMethod = this.local.bind(this);
|
|
1264
|
+
|
|
1265
|
+
// Add component methods to local
|
|
1266
|
+
(localMethod as any).x = function (
|
|
1267
|
+
value?: SignalValue<number>,
|
|
1268
|
+
duration?: number,
|
|
1269
|
+
timingFunction?: TimingFunction,
|
|
1270
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1271
|
+
): number | TOwner | ThreadGenerator {
|
|
1272
|
+
if (arguments.length === 0) {
|
|
1273
|
+
return localMethod().x;
|
|
1274
|
+
}
|
|
1275
|
+
// Get the current local position once and preserve the y component
|
|
1276
|
+
const currentLocalPos = localMethod();
|
|
1277
|
+
const newLocalPos = new Vector2([unwrap(value!), currentLocalPos.y]);
|
|
1278
|
+
if (arguments.length === 1) {
|
|
1279
|
+
return localMethod(newLocalPos);
|
|
1280
|
+
}
|
|
1281
|
+
return localMethod(
|
|
1282
|
+
newLocalPos,
|
|
1283
|
+
duration!,
|
|
1284
|
+
timingFunction,
|
|
1285
|
+
interpolationFunction as any,
|
|
1286
|
+
) as ThreadGenerator;
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
(localMethod as any).y = function (
|
|
1290
|
+
value?: SignalValue<number>,
|
|
1291
|
+
duration?: number,
|
|
1292
|
+
timingFunction?: TimingFunction,
|
|
1293
|
+
interpolationFunction?: InterpolationFunction<number>,
|
|
1294
|
+
): number | TOwner | ThreadGenerator {
|
|
1295
|
+
if (arguments.length === 0) {
|
|
1296
|
+
return localMethod().y;
|
|
1297
|
+
}
|
|
1298
|
+
// Get the current local position once and preserve the x component
|
|
1299
|
+
const currentLocalPos = localMethod();
|
|
1300
|
+
const newLocalPos = new Vector2([currentLocalPos.x, unwrap(value!)]);
|
|
1301
|
+
if (arguments.length === 1) {
|
|
1302
|
+
return localMethod(newLocalPos);
|
|
1303
|
+
}
|
|
1304
|
+
return localMethod(
|
|
1305
|
+
newLocalPos,
|
|
1306
|
+
duration!,
|
|
1307
|
+
timingFunction,
|
|
1308
|
+
interpolationFunction as any,
|
|
1309
|
+
) as ThreadGenerator;
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
Object.defineProperty(this.invokable, 'local', {
|
|
1313
|
+
value: localMethod,
|
|
1314
|
+
enumerable: false,
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Override get to use custom getter if available.
|
|
1320
|
+
* This enables computed layout positions (e.g., top, left) to calculate their values dynamically.
|
|
1321
|
+
*/
|
|
1322
|
+
public override get(): Vector2 {
|
|
1323
|
+
if (this.extensions.getter) {
|
|
1324
|
+
return this.extensions.getter();
|
|
1325
|
+
}
|
|
1326
|
+
return super.get();
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* Set a custom setter function for layout position delegation.
|
|
1331
|
+
* This allows layout signals to delegate setting behavior to position updates.
|
|
1332
|
+
*/
|
|
1333
|
+
public setCustomSetter(setterFunc: LayoutPositionSetter<TOwner>): void {
|
|
1334
|
+
this.extensions.setter = setterFunc;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* Override invoke to handle layout-specific delegation patterns.
|
|
1339
|
+
* Uses custom setter for simple value assignments when available.
|
|
1340
|
+
*/
|
|
1341
|
+
public override invoke(
|
|
1342
|
+
value?: SignalValue<PossibleVector2> | typeof DEFAULT,
|
|
1343
|
+
duration?: number,
|
|
1344
|
+
timingFunction?: TimingFunction,
|
|
1345
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1346
|
+
): Vector2 | TOwner | SignalGenerator<PossibleVector2, Vector2> {
|
|
1347
|
+
// Getter: no arguments
|
|
1348
|
+
if (arguments.length === GETTER_ARGS) {
|
|
1349
|
+
return this.get();
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// Setter: single argument with custom delegate
|
|
1353
|
+
if (
|
|
1354
|
+
arguments.length === SETTER_ARGS &&
|
|
1355
|
+
this.hasCustomDelegate &&
|
|
1356
|
+
this.extensions.setter
|
|
1357
|
+
) {
|
|
1358
|
+
const result = this.extensions.setter(value!);
|
|
1359
|
+
return result !== undefined ? result : this.owner;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// Animation or fallback to default behavior
|
|
1363
|
+
return super.invoke(value, duration, timingFunction, interpolationFunction);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
public override toSignal(): LayoutPositionSignal<TOwner> {
|
|
1367
|
+
return this.invokable as LayoutPositionSignal<TOwner>;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
public abs(): Vector2;
|
|
1371
|
+
public abs(value: SignalValue<PossibleVector2>): TOwner;
|
|
1372
|
+
public abs(
|
|
1373
|
+
value: SignalValue<PossibleVector2>,
|
|
1374
|
+
duration: number,
|
|
1375
|
+
timingFunction?: TimingFunction,
|
|
1376
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1377
|
+
): SignalGenerator<PossibleVector2, Vector2>;
|
|
1378
|
+
public abs(
|
|
1379
|
+
value?: SignalValue<PossibleVector2>,
|
|
1380
|
+
duration?: number,
|
|
1381
|
+
timingFunction?: TimingFunction,
|
|
1382
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1383
|
+
): Vector2 | TOwner | SignalGenerator<PossibleVector2, Vector2> {
|
|
1384
|
+
if (arguments.length === 0) {
|
|
1385
|
+
// For layout position signals, get the current computed value
|
|
1386
|
+
const currentValue = this.get();
|
|
1387
|
+
return currentValue.transformAsPoint(this.owner.localToWorld());
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Convert absolute value to local value for this layout position and set it
|
|
1391
|
+
const localValue = TransformConverter.absoluteToLocalLayoutPosition(
|
|
1392
|
+
this.owner,
|
|
1393
|
+
value!,
|
|
1394
|
+
);
|
|
1395
|
+
return this.invoke(
|
|
1396
|
+
localValue,
|
|
1397
|
+
duration,
|
|
1398
|
+
timingFunction,
|
|
1399
|
+
interpolationFunction,
|
|
1400
|
+
) as TOwner | SignalGenerator<PossibleVector2, Vector2>;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
public relativeTo(node: Node): Vector2;
|
|
1404
|
+
public relativeTo(node: Node, value: SignalValue<PossibleVector2>): TOwner;
|
|
1405
|
+
public relativeTo(
|
|
1406
|
+
node: Node,
|
|
1407
|
+
value: SignalValue<PossibleVector2>,
|
|
1408
|
+
duration: number,
|
|
1409
|
+
timingFunction?: TimingFunction,
|
|
1410
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1411
|
+
): SignalGenerator<PossibleVector2, Vector2>;
|
|
1412
|
+
public relativeTo(
|
|
1413
|
+
node: Node,
|
|
1414
|
+
value?: SignalValue<PossibleVector2>,
|
|
1415
|
+
duration?: number,
|
|
1416
|
+
timingFunction?: TimingFunction,
|
|
1417
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1418
|
+
): Vector2 | TOwner | SignalGenerator<PossibleVector2, Vector2> {
|
|
1419
|
+
if (arguments.length === 1) {
|
|
1420
|
+
// For layout positions, calculate relative position using absolute coordinates
|
|
1421
|
+
const currentValue = this.get();
|
|
1422
|
+
const absPosition = currentValue.transformAsPoint(
|
|
1423
|
+
this.owner.localToWorld(),
|
|
1424
|
+
);
|
|
1425
|
+
const targetAbsPosition = node.absolutePosition();
|
|
1426
|
+
return absPosition.sub(targetAbsPosition);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// Convert relative value to local value and set it
|
|
1430
|
+
const absoluteValue = TransformConverter.relativeToAbsolutePosition(
|
|
1431
|
+
node,
|
|
1432
|
+
value!,
|
|
1433
|
+
);
|
|
1434
|
+
const localValue = TransformConverter.absoluteToLocalLayoutPosition(
|
|
1435
|
+
this.owner,
|
|
1436
|
+
absoluteValue,
|
|
1437
|
+
);
|
|
1438
|
+
return this.invoke(
|
|
1439
|
+
localValue,
|
|
1440
|
+
duration,
|
|
1441
|
+
timingFunction,
|
|
1442
|
+
interpolationFunction,
|
|
1443
|
+
) as TOwner | SignalGenerator<PossibleVector2, Vector2>;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
public view(): Vector2;
|
|
1447
|
+
public view(value: SignalValue<PossibleVector2>): TOwner;
|
|
1448
|
+
public view(
|
|
1449
|
+
value: SignalValue<PossibleVector2>,
|
|
1450
|
+
duration: number,
|
|
1451
|
+
timingFunction?: TimingFunction,
|
|
1452
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1453
|
+
): SignalGenerator<PossibleVector2, Vector2>;
|
|
1454
|
+
public view(
|
|
1455
|
+
value?: SignalValue<PossibleVector2>,
|
|
1456
|
+
duration?: number,
|
|
1457
|
+
timingFunction?: TimingFunction,
|
|
1458
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1459
|
+
): Vector2 | TOwner | SignalGenerator<PossibleVector2, Vector2> {
|
|
1460
|
+
if (arguments.length === 0) {
|
|
1461
|
+
const currentValue = this.get();
|
|
1462
|
+
return currentValue.transformAsPoint(this.owner.view().localToWorld());
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// Convert view space value to local value and set it
|
|
1466
|
+
const localValue = TransformConverter.viewToLocalPosition(
|
|
1467
|
+
this.owner,
|
|
1468
|
+
value!,
|
|
1469
|
+
);
|
|
1470
|
+
return this.invoke(
|
|
1471
|
+
localValue,
|
|
1472
|
+
duration,
|
|
1473
|
+
timingFunction,
|
|
1474
|
+
interpolationFunction,
|
|
1475
|
+
) as TOwner | SignalGenerator<PossibleVector2, Vector2>;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
public local(): Vector2;
|
|
1479
|
+
public local(value: SignalValue<PossibleVector2>): TOwner;
|
|
1480
|
+
public local(
|
|
1481
|
+
value: SignalValue<PossibleVector2>,
|
|
1482
|
+
duration: number,
|
|
1483
|
+
timingFunction?: TimingFunction,
|
|
1484
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1485
|
+
): SignalGenerator<PossibleVector2, Vector2>;
|
|
1486
|
+
public local(
|
|
1487
|
+
value?: SignalValue<PossibleVector2>,
|
|
1488
|
+
duration?: number,
|
|
1489
|
+
timingFunction?: TimingFunction,
|
|
1490
|
+
interpolationFunction?: InterpolationFunction<Vector2>,
|
|
1491
|
+
): Vector2 | TOwner | SignalGenerator<PossibleVector2, Vector2> {
|
|
1492
|
+
if (arguments.length === 0) {
|
|
1493
|
+
return this.get();
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// Local value is just the raw value
|
|
1497
|
+
return this.invoke(
|
|
1498
|
+
value!,
|
|
1499
|
+
duration,
|
|
1500
|
+
timingFunction,
|
|
1501
|
+
interpolationFunction,
|
|
1502
|
+
) as TOwner | SignalGenerator<PossibleVector2, Vector2>;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* Creates an enhanced position signal with coordinate space transformation methods.
|
|
1508
|
+
*
|
|
1509
|
+
* This decorator creates a position signal that supports operations in multiple coordinate spaces:
|
|
1510
|
+
* - **Local**: Relative to the node's parent
|
|
1511
|
+
* - **Absolute**: In the global scene coordinates
|
|
1512
|
+
* - **View**: In the camera/view coordinate system
|
|
1513
|
+
* - **Relative**: Relative to another specific node
|
|
1514
|
+
*
|
|
1515
|
+
* @param prefix - Optional prefix for the underlying X/Y property names.
|
|
1516
|
+
* Can be a string (e.g., 'scale' creates 'scaleX'/'scaleY') or
|
|
1517
|
+
* an object mapping \{x: 'propX', y: 'propY'\}
|
|
1518
|
+
*
|
|
1519
|
+
* @example
|
|
1520
|
+
* ```typescript
|
|
1521
|
+
* class MyNode extends Node {
|
|
1522
|
+
* \@positionSignal()
|
|
1523
|
+
* declare readonly position: PositionSignal<this>;
|
|
1524
|
+
*
|
|
1525
|
+
* \@positionSignal('anchor')
|
|
1526
|
+
* declare readonly anchor: PositionSignal<this>; // Uses anchorX/anchorY
|
|
1527
|
+
* }
|
|
1528
|
+
* ```
|
|
1529
|
+
*/
|
|
1530
|
+
export function positionSignal(
|
|
1531
|
+
prefix?: string | Record<string, string>,
|
|
1532
|
+
): PropertyDecorator {
|
|
1533
|
+
return (target, key) => {
|
|
1534
|
+
compound(
|
|
1535
|
+
typeof prefix === 'object'
|
|
1536
|
+
? prefix
|
|
1537
|
+
: {
|
|
1538
|
+
x: prefix ? `${prefix}X` : 'x',
|
|
1539
|
+
y: prefix ? `${prefix}Y` : 'y',
|
|
1540
|
+
},
|
|
1541
|
+
PositionSignalContext,
|
|
1542
|
+
)(target, key);
|
|
1543
|
+
wrapper(Vector2)(target, key);
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Creates an enhanced scale signal with coordinate space transformation methods.
|
|
1549
|
+
*
|
|
1550
|
+
* Scale signals support operations in multiple coordinate spaces:
|
|
1551
|
+
* - **Local**: Scale relative to the node's parent
|
|
1552
|
+
* - **Absolute**: Scale in global scene coordinates
|
|
1553
|
+
* - **View**: Scale in camera/view coordinate system
|
|
1554
|
+
* - **Relative**: Scale relative to another node's scale
|
|
1555
|
+
*
|
|
1556
|
+
* @param prefix - Optional prefix for the underlying X/Y property names
|
|
1557
|
+
*
|
|
1558
|
+
* @example
|
|
1559
|
+
* ```typescript
|
|
1560
|
+
* class MyNode extends Node {
|
|
1561
|
+
* \@scaleSignal('scale')
|
|
1562
|
+
* declare readonly scale: ScaleSignal<this>;
|
|
1563
|
+
* }
|
|
1564
|
+
*
|
|
1565
|
+
* // Usage:
|
|
1566
|
+
* node.scale([2, 1.5]); // Local scale
|
|
1567
|
+
* node.scale.abs([4, 3]); // Absolute scale
|
|
1568
|
+
* node.scale.relativeTo(parent, [0.5, 0.5]); // Scale relative to parent
|
|
1569
|
+
* ```
|
|
1570
|
+
*/
|
|
1571
|
+
export function scaleSignal(
|
|
1572
|
+
prefix?: string | Record<string, string>,
|
|
1573
|
+
): PropertyDecorator {
|
|
1574
|
+
return (target, key) => {
|
|
1575
|
+
compound(
|
|
1576
|
+
typeof prefix === 'object'
|
|
1577
|
+
? prefix
|
|
1578
|
+
: {
|
|
1579
|
+
x: prefix ? `${prefix}X` : 'x',
|
|
1580
|
+
y: prefix ? `${prefix}Y` : 'y',
|
|
1581
|
+
},
|
|
1582
|
+
ScaleSignalContext,
|
|
1583
|
+
)(target, key);
|
|
1584
|
+
wrapper(Vector2)(target, key);
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Creates an enhanced rotation signal with coordinate space transformation methods.
|
|
1590
|
+
*
|
|
1591
|
+
* Rotation signals work with degrees and support operations in multiple coordinate spaces:
|
|
1592
|
+
* - **Local**: Rotation relative to the node's parent
|
|
1593
|
+
* - **Absolute**: Rotation in global scene coordinates
|
|
1594
|
+
* - **View**: Rotation in camera/view coordinate system
|
|
1595
|
+
* - **Relative**: Rotation relative to another node's rotation
|
|
1596
|
+
*
|
|
1597
|
+
* @example
|
|
1598
|
+
* ```typescript
|
|
1599
|
+
* class MyNode extends Node {
|
|
1600
|
+
* \@rotationSignal()
|
|
1601
|
+
* declare readonly rotation: RotationSignal<this>;
|
|
1602
|
+
* }
|
|
1603
|
+
*
|
|
1604
|
+
* // Usage:
|
|
1605
|
+
* node.rotation(45); // Local rotation (45 degrees)
|
|
1606
|
+
* node.rotation.abs(90); // Absolute rotation (90 degrees)
|
|
1607
|
+
* node.rotation.relativeTo(other, 180); // 180 degrees relative to other node
|
|
1608
|
+
* ```
|
|
1609
|
+
*/
|
|
1610
|
+
export function rotationSignal(): PropertyDecorator {
|
|
1611
|
+
return (target, key) => {
|
|
1612
|
+
const meta = getPropertyMetaOrCreate<number>(target, key);
|
|
1613
|
+
addInitializer(target, (instance: Node) => {
|
|
1614
|
+
const initial = meta.default;
|
|
1615
|
+
const parser = meta.parser?.bind(instance) ?? ((value: number) => value);
|
|
1616
|
+
const signalContext = new RotationSignalContext(
|
|
1617
|
+
initial,
|
|
1618
|
+
meta.interpolationFunction ?? deepLerp,
|
|
1619
|
+
instance,
|
|
1620
|
+
parser,
|
|
1621
|
+
makeSignalExtensions(meta, instance, key as string),
|
|
1622
|
+
);
|
|
1623
|
+
|
|
1624
|
+
// Use Object.defineProperty to avoid casting
|
|
1625
|
+
Object.defineProperty(instance, key, {
|
|
1626
|
+
value: signalContext.toSignal(),
|
|
1627
|
+
writable: false,
|
|
1628
|
+
enumerable: true,
|
|
1629
|
+
configurable: false,
|
|
1630
|
+
});
|
|
1631
|
+
});
|
|
1632
|
+
};
|
|
1633
|
+
}
|