@fulate/core 1.0.6 → 1.0.8
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/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,12 @@ export type { EventName, EventCallback, FulateEvent, FulateEventDetail, CustomEv
|
|
|
3
3
|
export { Node, NodeLifecycleEvent } from "./node/node";
|
|
4
4
|
export type { NodeCreationOptions } from "./node/node";
|
|
5
5
|
export { ElementTransform } from "./node/element-transform";
|
|
6
|
-
export type { ElementTransformClass,
|
|
6
|
+
export type { ElementTransformClass, GeometryBoundsView } from "./node/element-transform";
|
|
7
7
|
export { ChangeImpact, NodeProperties, resolveOrigin, TransformProperties } from "./node/properties";
|
|
8
8
|
export type { OriginX, OriginY, PropertyChanges, PropertyUpdates, TransformableOptions } from "./node/properties";
|
|
9
9
|
export { Element, ElementProperties } from "./node/element";
|
|
10
10
|
export type { AnyElement, BaseElementOption, ElementJSON, ElementOptionPatch, AnimateOptions, AnimateTarget } from "./node/element";
|
|
11
|
-
export { isReparentTransformIntent, resolveReferenceTransform, resolveReferenceTransformTarget } from "./node/reference-transform";
|
|
11
|
+
export { isReparentTransformIntent, resolveAuthoredGeometryFramePatch, resolveReferenceTransform, resolveReferenceTransformTarget } from "./node/reference-transform";
|
|
12
12
|
export type { ElementTransformBaseline, ReferenceTransformBaseline, ReferenceTransformFrame, ReferenceTransformResolution, ReferenceTransformTarget, ReparentTransformIntent, TransformIntent } from "./node/reference-transform";
|
|
13
13
|
export { DEFAULT_CONNECTION_PORT_SCHEMA, getElementConnectionPort, getElementConnectionPortPoint, getElementConnectionPortPoints, projectConnectionPortPoint } from "./utils/connection-port";
|
|
14
14
|
export type { ConnectionPortId, ConnectionPortConfiguration, ConnectionPortDefinition, ConnectionPortLabelStyle, ConnectionPortDirection, ConnectionPortLimit, ConnectionPortLimits, ConnectionPortRef, DerivedConnectionPort, EdgeConnectionPortPosition, ResolvedConnectionPort, ResolvedConnectionPortSlot } from "./utils/connection-port";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clamp, isEqual, isFunction, isNull, isNumber, isObjectLike, isString, isUndefined } from "lodash-es";
|
|
2
|
-
import { Bound, Intersection, clipPolygonToRect, createCanvasGradient, createPointView, formatPremultipliedColor, isGradient, makeBoundingBoxFromRects, parsePremultipliedColor, qrDecompose, rectEdgePosition, transformPoint } from "@fulate/share";
|
|
2
|
+
import { Bound, Intersection, clipPolygonToRect, createCanvasGradient, createPointView, formatPremultipliedColor, isGradient, isIdentityMatrix, makeBoundingBoxFromRects, parsePremultipliedColor, qrDecompose, rectEdgePosition, transformPoint } from "@fulate/share";
|
|
3
3
|
import RBush from "rbush";
|
|
4
4
|
import { Easing, Group, Tween } from "@tweenjs/tween.js";
|
|
5
5
|
//#region packages/core/src/event.ts
|
|
@@ -1790,6 +1790,36 @@ function createGeometryQuery(snapshot) {
|
|
|
1790
1790
|
return snapshot instanceof RectangleGeometrySnapshot ? new RectangleSnapshotGeometryQuery(snapshot) : new SnapshotGeometryQuery(snapshot);
|
|
1791
1791
|
}
|
|
1792
1792
|
//#endregion
|
|
1793
|
+
//#region packages/core/src/node/element-transform-geometry.ts
|
|
1794
|
+
/** Project a local rectangle into a matrix space and return its AABB. */
|
|
1795
|
+
function projectBounds(matrix, bounds) {
|
|
1796
|
+
if (!bounds) return new Bound();
|
|
1797
|
+
const left = bounds.left;
|
|
1798
|
+
const top = bounds.top;
|
|
1799
|
+
const right = left + bounds.width;
|
|
1800
|
+
const bottom = top + bounds.height;
|
|
1801
|
+
if (matrix.b === 0 && matrix.c === 0) {
|
|
1802
|
+
const firstX = matrix.a * left + matrix.e;
|
|
1803
|
+
const secondX = matrix.a * right + matrix.e;
|
|
1804
|
+
const firstY = matrix.d * top + matrix.f;
|
|
1805
|
+
const secondY = matrix.d * bottom + matrix.f;
|
|
1806
|
+
return new Bound(Math.min(firstX, secondX), Math.min(firstY, secondY), Math.max(firstX, secondX), Math.max(firstY, secondY));
|
|
1807
|
+
}
|
|
1808
|
+
const x0 = matrix.a * left + matrix.c * top + matrix.e;
|
|
1809
|
+
const x1 = matrix.a * right + matrix.c * top + matrix.e;
|
|
1810
|
+
const x2 = matrix.a * right + matrix.c * bottom + matrix.e;
|
|
1811
|
+
const x3 = matrix.a * left + matrix.c * bottom + matrix.e;
|
|
1812
|
+
const y0 = matrix.b * left + matrix.d * top + matrix.f;
|
|
1813
|
+
const y1 = matrix.b * right + matrix.d * top + matrix.f;
|
|
1814
|
+
const y2 = matrix.b * right + matrix.d * bottom + matrix.f;
|
|
1815
|
+
const y3 = matrix.b * left + matrix.d * bottom + matrix.f;
|
|
1816
|
+
return new Bound(Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3), Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3));
|
|
1817
|
+
}
|
|
1818
|
+
/** Compare a committed matrix with a previous scalar matrix sample. */
|
|
1819
|
+
function matrixChanged(matrix, previousA, previousB, previousC, previousD, previousE, previousF) {
|
|
1820
|
+
return matrix.a !== previousA || matrix.b !== previousB || matrix.c !== previousC || matrix.d !== previousD || matrix.e !== previousE || matrix.f !== previousF;
|
|
1821
|
+
}
|
|
1822
|
+
//#endregion
|
|
1793
1823
|
//#region packages/core/src/node/element-transform.ts
|
|
1794
1824
|
var VISUAL_GEOMETRY_IMPACT$1 = ChangeImpact.LocalGeometry | ChangeImpact.WorldTransform | ChangeImpact.VisualBounds;
|
|
1795
1825
|
var GEOMETRY_CACHE_IMPACT = ChangeImpact.LocalGeometry | ChangeImpact.WorldTransform | ChangeImpact.VisualBounds | ChangeImpact.HitBounds;
|
|
@@ -2051,7 +2081,7 @@ var ElementTransform = class {
|
|
|
2051
2081
|
/** 清除 world 投影;world-dependent local geometry 只在矩阵实际变化时失效。 */
|
|
2052
2082
|
finishWorldMatrixCommit(previousA, previousB, previousC, previousD, previousE, previousF) {
|
|
2053
2083
|
const matrix = this.worldMatrix;
|
|
2054
|
-
const changed = matrix
|
|
2084
|
+
const changed = matrixChanged(matrix, previousA, previousB, previousC, previousD, previousE, previousF);
|
|
2055
2085
|
if (this.geometrySnapshot && this.worldMatrixAffectsLocalGeometry() && changed) this.invalidateCache(ChangeImpact.LocalGeometry);
|
|
2056
2086
|
else this.clearWorldProjection();
|
|
2057
2087
|
return changed;
|
|
@@ -2224,27 +2254,7 @@ var ElementTransform = class {
|
|
|
2224
2254
|
}
|
|
2225
2255
|
/** 将 local AABB 四角投影到指定 reference matrix。 */
|
|
2226
2256
|
toBounds(matrix, bounds) {
|
|
2227
|
-
|
|
2228
|
-
const left = bounds.left;
|
|
2229
|
-
const top = bounds.top;
|
|
2230
|
-
const right = left + bounds.width;
|
|
2231
|
-
const bottom = top + bounds.height;
|
|
2232
|
-
if (matrix.b === 0 && matrix.c === 0) {
|
|
2233
|
-
const firstX = matrix.a * left + matrix.e;
|
|
2234
|
-
const secondX = matrix.a * right + matrix.e;
|
|
2235
|
-
const firstY = matrix.d * top + matrix.f;
|
|
2236
|
-
const secondY = matrix.d * bottom + matrix.f;
|
|
2237
|
-
return new Bound(Math.min(firstX, secondX), Math.min(firstY, secondY), Math.max(firstX, secondX), Math.max(firstY, secondY));
|
|
2238
|
-
}
|
|
2239
|
-
const x0 = matrix.a * left + matrix.c * top + matrix.e;
|
|
2240
|
-
const x1 = matrix.a * right + matrix.c * top + matrix.e;
|
|
2241
|
-
const x2 = matrix.a * right + matrix.c * bottom + matrix.e;
|
|
2242
|
-
const x3 = matrix.a * left + matrix.c * bottom + matrix.e;
|
|
2243
|
-
const y0 = matrix.b * left + matrix.d * top + matrix.f;
|
|
2244
|
-
const y1 = matrix.b * right + matrix.d * top + matrix.f;
|
|
2245
|
-
const y2 = matrix.b * right + matrix.d * bottom + matrix.f;
|
|
2246
|
-
const y3 = matrix.b * left + matrix.d * bottom + matrix.f;
|
|
2247
|
-
return new Bound(Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3), Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3));
|
|
2257
|
+
return projectBounds(matrix, bounds);
|
|
2248
2258
|
}
|
|
2249
2259
|
/** 惰性构建当前快照对应的 local 绘制轮廓。 */
|
|
2250
2260
|
getVisualOutline() {
|
|
@@ -2256,7 +2266,7 @@ var ElementTransform = class {
|
|
|
2256
2266
|
}
|
|
2257
2267
|
/** 在清理 visual 缓存前保存最后一次提交的 world 绘制范围。 */
|
|
2258
2268
|
captureCurrentVisualBounds() {
|
|
2259
|
-
if (this.hasGeometry()) this.captureVisualBounds(this.
|
|
2269
|
+
if (this.worldVisualBounds && this.hasGeometry()) this.captureVisualBounds(this.worldVisualBounds);
|
|
2260
2270
|
}
|
|
2261
2271
|
/** 将旧 visual bounds交给当前 transform 和所属 Layer 的 damage 跟踪。 */
|
|
2262
2272
|
captureVisualBounds(bounds) {
|
|
@@ -2284,6 +2294,7 @@ var ElementTransform = class {
|
|
|
2284
2294
|
invalidate(impact, previousBounds) {
|
|
2285
2295
|
if (impact & CHILD_LAYOUT_IMPACT) this.childLayoutDirty = true;
|
|
2286
2296
|
const visualGeometryChanged = (impact & VISUAL_GEOMETRY_IMPACT$1) !== 0;
|
|
2297
|
+
const invalidateAncestors = !this.needsCommit;
|
|
2287
2298
|
if (!this.needsCommit) {
|
|
2288
2299
|
if (visualGeometryChanged) {
|
|
2289
2300
|
if (isUndefined(previousBounds)) this.captureCurrentVisualBounds();
|
|
@@ -2292,14 +2303,20 @@ var ElementTransform = class {
|
|
|
2292
2303
|
this.hitOnlyInvalidation = !visualGeometryChanged && (impact & ChangeImpact.HitBounds) !== 0;
|
|
2293
2304
|
this.needsCommit = true;
|
|
2294
2305
|
this.currentRenderCoverageBounds = void 0;
|
|
2295
|
-
this.markDirtyAncestors();
|
|
2296
2306
|
} else if (this.hitOnlyInvalidation && visualGeometryChanged) {
|
|
2297
2307
|
this.captureCurrentVisualBounds();
|
|
2298
2308
|
this.hitOnlyInvalidation = false;
|
|
2299
2309
|
}
|
|
2300
2310
|
this.invalidateCache(impact);
|
|
2311
|
+
if (invalidateAncestors) this.markDirtyAncestors();
|
|
2301
2312
|
if (this.element.isActive && !this.resolvingFitSize) this.element.layer.invalidateLayout(this.element);
|
|
2302
2313
|
}
|
|
2314
|
+
/** A derived layout can change visual bounds during an already pending commit. @internal */
|
|
2315
|
+
invalidateLayoutBounds() {
|
|
2316
|
+
const pending = this.needsCommit;
|
|
2317
|
+
this.invalidate(ChangeImpact.VisualBounds);
|
|
2318
|
+
if (pending) this.markDirtyAncestors();
|
|
2319
|
+
}
|
|
2303
2320
|
/** 标记 local 几何需要重新构建。 */
|
|
2304
2321
|
markGeometryNeedsLayout(notifyScene = true) {
|
|
2305
2322
|
const publish = !this.needsCommit;
|
|
@@ -2484,7 +2501,7 @@ var ElementTransform = class {
|
|
|
2484
2501
|
const element = this.element;
|
|
2485
2502
|
const cacheable = !this.needsCommit && !this.hasDirtyDescendant && element.activeRoot?.frameRuntime.isStabilizingGeometry !== true;
|
|
2486
2503
|
if (cacheable && !isUndefined(this.currentRenderCoverageBounds)) return this.currentRenderCoverageBounds;
|
|
2487
|
-
const ownBounds = this.hasGeometry() ? this.
|
|
2504
|
+
const ownBounds = this.hasGeometry() ? this.getWorldVisualBoundsView() : null;
|
|
2488
2505
|
let descendantBounds = null;
|
|
2489
2506
|
for (const child of element.children) {
|
|
2490
2507
|
if (!child.visible) continue;
|
|
@@ -2506,7 +2523,7 @@ var ElementTransform = class {
|
|
|
2506
2523
|
descendantBounds.maxY = Math.min(descendantBounds.maxY, clipBounds.maxY);
|
|
2507
2524
|
if (descendantBounds.minX >= descendantBounds.maxX || descendantBounds.minY >= descendantBounds.maxY) descendantBounds = null;
|
|
2508
2525
|
}
|
|
2509
|
-
const bounds = ownBounds && descendantBounds ? ownBounds.merge(descendantBounds) : ownBounds ?? descendantBounds;
|
|
2526
|
+
const bounds = ownBounds && descendantBounds ? new Bound(ownBounds.minX, ownBounds.minY, ownBounds.maxX, ownBounds.maxY).merge(descendantBounds) : ownBounds ?? descendantBounds;
|
|
2510
2527
|
if (cacheable) this.currentRenderCoverageBounds = bounds;
|
|
2511
2528
|
return bounds;
|
|
2512
2529
|
}
|
|
@@ -3046,7 +3063,7 @@ var ElementProperties = class extends TransformProperties {
|
|
|
3046
3063
|
}
|
|
3047
3064
|
};
|
|
3048
3065
|
/** 可变换、可绘制、可命中并支持事件和动画的场景节点。 */
|
|
3049
|
-
var Element = class extends Node {
|
|
3066
|
+
var Element = class Element extends Node {
|
|
3050
3067
|
type = "element";
|
|
3051
3068
|
/** 当前 Element 唯一的变换与几何运行时。 */
|
|
3052
3069
|
transform;
|
|
@@ -3158,6 +3175,7 @@ var Element = class extends Node {
|
|
|
3158
3175
|
* @param value `false`、`true` 或 custom definitions。
|
|
3159
3176
|
*/
|
|
3160
3177
|
replacePorts(value) {
|
|
3178
|
+
if (this.ports === value && Element.getConfiguredPropertyValue(this, "ports") === value) return this;
|
|
3161
3179
|
this.commitConfiguredOptions({ ports: value });
|
|
3162
3180
|
return this;
|
|
3163
3181
|
}
|
|
@@ -3509,11 +3527,27 @@ var Element = class extends Node {
|
|
|
3509
3527
|
}
|
|
3510
3528
|
/** @internal 一次产出 custom Port position、法线与 label edge 长度。 */
|
|
3511
3529
|
getConnectionPortGeometry(edge, ratio) {
|
|
3512
|
-
const
|
|
3530
|
+
const position = this.getConnectionPortPosition(edge, ratio);
|
|
3531
|
+
let nx = 0;
|
|
3532
|
+
let ny = -1;
|
|
3533
|
+
switch (edge) {
|
|
3534
|
+
case "right":
|
|
3535
|
+
nx = 1;
|
|
3536
|
+
ny = 0;
|
|
3537
|
+
break;
|
|
3538
|
+
case "bottom":
|
|
3539
|
+
nx = 0;
|
|
3540
|
+
ny = 1;
|
|
3541
|
+
break;
|
|
3542
|
+
case "left":
|
|
3543
|
+
nx = -1;
|
|
3544
|
+
ny = 0;
|
|
3545
|
+
break;
|
|
3546
|
+
}
|
|
3513
3547
|
return {
|
|
3514
|
-
position
|
|
3515
|
-
nx
|
|
3516
|
-
ny
|
|
3548
|
+
position,
|
|
3549
|
+
nx,
|
|
3550
|
+
ny,
|
|
3517
3551
|
edgeLength: edge === "top" || edge === "bottom" ? Math.abs(this.width ?? 0) : Math.abs(this.height ?? 0)
|
|
3518
3552
|
};
|
|
3519
3553
|
}
|
|
@@ -3665,7 +3699,7 @@ var IDENTITY_REFERENCE_TRANSFORM = new DOMMatrix();
|
|
|
3665
3699
|
*/
|
|
3666
3700
|
function resolveSparseReferenceTranslation(baseline, intent, targetParentWorldMatrix) {
|
|
3667
3701
|
const delta = intent.worldDelta;
|
|
3668
|
-
if (delta
|
|
3702
|
+
if (!sameLinearMatrix(delta, IDENTITY_REFERENCE_TRANSFORM) || !sameLinearMatrix(baseline.startParentWorldMatrix, targetParentWorldMatrix)) return null;
|
|
3669
3703
|
const worldX = delta.e + baseline.startParentWorldMatrix.e - targetParentWorldMatrix.e;
|
|
3670
3704
|
const worldY = delta.f + baseline.startParentWorldMatrix.f - targetParentWorldMatrix.f;
|
|
3671
3705
|
const parentInverse = baseline.startParentWorldInverseMatrix;
|
|
@@ -3683,6 +3717,107 @@ function sameMatrix(left, right) {
|
|
|
3683
3717
|
function sameLinearMatrix(left, right) {
|
|
3684
3718
|
return left.a === right.a && left.b === right.b && left.c === right.c && left.d === right.d;
|
|
3685
3719
|
}
|
|
3720
|
+
function createFrameLinearMatrix(frame) {
|
|
3721
|
+
return new DOMMatrix().rotate(0, 0, frame.angle).skewX(frame.skewX).skewY(frame.skewY).scale(frame.scaleX, frame.scaleY);
|
|
3722
|
+
}
|
|
3723
|
+
function transformReferenceBounds(bounds, matrix) {
|
|
3724
|
+
const right = bounds.left + bounds.width;
|
|
3725
|
+
const bottom = bounds.top + bounds.height;
|
|
3726
|
+
const corners = [
|
|
3727
|
+
transformPoint(matrix, {
|
|
3728
|
+
x: bounds.left,
|
|
3729
|
+
y: bounds.top
|
|
3730
|
+
}),
|
|
3731
|
+
transformPoint(matrix, {
|
|
3732
|
+
x: right,
|
|
3733
|
+
y: bounds.top
|
|
3734
|
+
}),
|
|
3735
|
+
transformPoint(matrix, {
|
|
3736
|
+
x: right,
|
|
3737
|
+
y: bottom
|
|
3738
|
+
}),
|
|
3739
|
+
transformPoint(matrix, {
|
|
3740
|
+
x: bounds.left,
|
|
3741
|
+
y: bottom
|
|
3742
|
+
})
|
|
3743
|
+
];
|
|
3744
|
+
let minX = corners[0].x;
|
|
3745
|
+
let minY = corners[0].y;
|
|
3746
|
+
let maxX = corners[0].x;
|
|
3747
|
+
let maxY = corners[0].y;
|
|
3748
|
+
for (let index = 1; index < corners.length; index++) {
|
|
3749
|
+
const point = corners[index];
|
|
3750
|
+
minX = Math.min(minX, point.x);
|
|
3751
|
+
minY = Math.min(minY, point.y);
|
|
3752
|
+
maxX = Math.max(maxX, point.x);
|
|
3753
|
+
maxY = Math.max(maxY, point.y);
|
|
3754
|
+
}
|
|
3755
|
+
return {
|
|
3756
|
+
left: minX,
|
|
3757
|
+
top: minY,
|
|
3758
|
+
width: maxX - minX,
|
|
3759
|
+
height: maxY - minY
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3762
|
+
/**
|
|
3763
|
+
* Factor a target local affine transform through the existing authored frame.
|
|
3764
|
+
* The frame keeps its current pose; the complete linear delta is written into
|
|
3765
|
+
* the vector's authored points. Translation remains a frame left/top patch so
|
|
3766
|
+
* no synthetic point offset is introduced into the canonical geometry.
|
|
3767
|
+
*/
|
|
3768
|
+
function resolveBakedAuthoredGeometryTransform(baseline, target) {
|
|
3769
|
+
const frameLinear = createFrameLinearMatrix(baseline.frame);
|
|
3770
|
+
const determinant = frameLinear.a * frameLinear.d - frameLinear.b * frameLinear.c;
|
|
3771
|
+
if (!Number.isFinite(determinant) || determinant === 0) return null;
|
|
3772
|
+
const targetLocal = target.parentInverseMatrix.multiply(target.worldMatrix);
|
|
3773
|
+
const targetLinear = new DOMMatrix([
|
|
3774
|
+
targetLocal.a,
|
|
3775
|
+
targetLocal.b,
|
|
3776
|
+
targetLocal.c,
|
|
3777
|
+
targetLocal.d,
|
|
3778
|
+
0,
|
|
3779
|
+
0
|
|
3780
|
+
]);
|
|
3781
|
+
const authoredLocalDelta = frameLinear.inverse().multiply(targetLinear);
|
|
3782
|
+
const nextBounds = transformReferenceBounds(baseline.referenceBounds, authoredLocalDelta);
|
|
3783
|
+
const offsetX = nextBounds.left + (resolveOrigin(baseline.frame.originX) + .5) * nextBounds.width;
|
|
3784
|
+
const offsetY = nextBounds.top + (resolveOrigin(baseline.frame.originY) + .5) * nextBounds.height;
|
|
3785
|
+
return {
|
|
3786
|
+
framePatch: {
|
|
3787
|
+
left: targetLocal.e - offsetX + frameLinear.a * offsetX + frameLinear.c * offsetY,
|
|
3788
|
+
top: targetLocal.f - offsetY + frameLinear.b * offsetX + frameLinear.d * offsetY,
|
|
3789
|
+
angle: baseline.frame.angle,
|
|
3790
|
+
scaleX: baseline.frame.scaleX,
|
|
3791
|
+
scaleY: baseline.frame.scaleY,
|
|
3792
|
+
skewX: baseline.frame.skewX,
|
|
3793
|
+
skewY: baseline.frame.skewY
|
|
3794
|
+
},
|
|
3795
|
+
authoredLocalDelta
|
|
3796
|
+
};
|
|
3797
|
+
}
|
|
3798
|
+
/**
|
|
3799
|
+
* 用 owner 已算出的精确 authored bounds 修正 bake frame 的位置。
|
|
3800
|
+
* AABB 经过旋转/错切后不能由原 bounds 四角精确推出;owner 在生成
|
|
3801
|
+
* 新几何后把真实 bounds 交回这里,保持 frame world matrix 与目标一致。
|
|
3802
|
+
*/
|
|
3803
|
+
function resolveAuthoredGeometryFramePatch(baseline, resolution, authoredBounds) {
|
|
3804
|
+
if (isIdentityMatrix(resolution.authoredLocalDelta)) return resolution.framePatch;
|
|
3805
|
+
const approximateBounds = transformReferenceBounds(baseline.referenceBounds, resolution.authoredLocalDelta);
|
|
3806
|
+
const originX = resolveOrigin(baseline.frame.originX) + .5;
|
|
3807
|
+
const originY = resolveOrigin(baseline.frame.originY) + .5;
|
|
3808
|
+
const approximateOffsetX = approximateBounds.left + originX * approximateBounds.width;
|
|
3809
|
+
const approximateOffsetY = approximateBounds.top + originY * approximateBounds.height;
|
|
3810
|
+
const authoredOffsetX = authoredBounds.left + originX * authoredBounds.width;
|
|
3811
|
+
const authoredOffsetY = authoredBounds.top + originY * authoredBounds.height;
|
|
3812
|
+
const deltaX = authoredOffsetX - approximateOffsetX;
|
|
3813
|
+
const deltaY = authoredOffsetY - approximateOffsetY;
|
|
3814
|
+
const frameLinear = createFrameLinearMatrix(baseline.frame);
|
|
3815
|
+
return {
|
|
3816
|
+
...resolution.framePatch,
|
|
3817
|
+
left: (resolution.framePatch.left ?? baseline.frame.left) + -deltaX + frameLinear.a * deltaX + frameLinear.c * deltaY,
|
|
3818
|
+
top: (resolution.framePatch.top ?? baseline.frame.top) + -deltaY + frameLinear.b * deltaX + frameLinear.d * deltaY
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3686
3821
|
/** 判断变换意图是否包含跨父级重挂载所需的目标父级矩阵。 */
|
|
3687
3822
|
function isReparentTransformIntent(intent) {
|
|
3688
3823
|
return "targetParentWorldMatrix" in intent;
|
|
@@ -3694,10 +3829,14 @@ function resolveTargetParentWorldMatrix(baseline, intent) {
|
|
|
3694
3829
|
function targetsFrozenReference(baseline, intent) {
|
|
3695
3830
|
const delta = intent.worldDelta;
|
|
3696
3831
|
const targetParentWorldMatrix = resolveTargetParentWorldMatrix(baseline, intent);
|
|
3697
|
-
return delta
|
|
3832
|
+
return isIdentityMatrix(delta) && sameMatrix(baseline.startParentWorldMatrix, targetParentWorldMatrix);
|
|
3698
3833
|
}
|
|
3699
3834
|
/** 从已持有的最终 world pose 纯计算 frame/authored delta。 */
|
|
3700
|
-
function resolveReferenceTransformTargetWithMode(baseline, target, preserveOwnedGeometry, unwrappedRotationDelta) {
|
|
3835
|
+
function resolveReferenceTransformTargetWithMode(baseline, target, preserveOwnedGeometry, unwrappedRotationDelta, bakeGeometry = false) {
|
|
3836
|
+
if (bakeGeometry && baseline.writeMode === "authored-geometry" && !preserveOwnedGeometry) {
|
|
3837
|
+
const baked = resolveBakedAuthoredGeometryTransform(baseline, target);
|
|
3838
|
+
if (baked) return baked;
|
|
3839
|
+
}
|
|
3701
3840
|
const targetDecomposition = qrDecompose(target.parentInverseMatrix.multiply(target.worldMatrix));
|
|
3702
3841
|
const targetAngle = unwrappedRotationDelta === void 0 ? targetDecomposition.angle : targetDecomposition.angle + 360 * Math.round((baseline.frame.angle + unwrappedRotationDelta - targetDecomposition.angle) / 360);
|
|
3703
3842
|
const authoredScaleX = preserveOwnedGeometry ? 1 : baseline.frame.scaleX === 0 ? 1 : Math.abs(targetDecomposition.scaleX / baseline.frame.scaleX);
|
|
@@ -3792,7 +3931,7 @@ function resolveReferenceTransform(baseline, intent) {
|
|
|
3792
3931
|
worldMatrix: intent.worldDelta.multiply(baseline.startWorldMatrix),
|
|
3793
3932
|
worldCenter: transformPoint(intent.worldDelta, startWorldCenter),
|
|
3794
3933
|
parentInverseMatrix: sameMatrix(baseline.startParentWorldMatrix, targetParentWorldMatrix) ? baseline.startParentWorldInverseMatrix : targetParentWorldMatrix.inverse()
|
|
3795
|
-
}, preserveOwnedGeometry, intent.unwrappedRotationDelta);
|
|
3934
|
+
}, preserveOwnedGeometry, intent.unwrappedRotationDelta, intent.bakeGeometry);
|
|
3796
3935
|
}
|
|
3797
3936
|
//#endregion
|
|
3798
3937
|
//#region packages/core/src/node/shape.ts
|
|
@@ -4143,20 +4282,24 @@ var Shape = class extends Element {
|
|
|
4143
4282
|
//#region packages/core/src/root/hit-test.ts
|
|
4144
4283
|
/** Fixed screen-space broad-phase aperture for scene point affordances. */
|
|
4145
4284
|
var POINTER_QUERY_RADIUS = 8;
|
|
4146
|
-
/**
|
|
4147
|
-
function
|
|
4285
|
+
/** 合并 pointer ancestor 状态与 child clip 的单次祖先遍历。 */
|
|
4286
|
+
function isPointerTargetWithinAncestorClips(root, element, point) {
|
|
4287
|
+
if (element.pickable === false || !isActiveVisibleNode(root, element) || element.silent) return false;
|
|
4148
4288
|
let ancestor = element.parent instanceof Element ? element.parent : void 0;
|
|
4149
4289
|
while (ancestor) {
|
|
4290
|
+
if (!isActiveVisibleNode(root, ancestor) || ancestor.silent) return false;
|
|
4150
4291
|
if (ancestor.getChildClipRect()) {
|
|
4151
4292
|
const localPoint = ancestor.transform.toLocal(point);
|
|
4152
4293
|
if (!localPoint || !ancestor.containsChildClipPoint(localPoint)) return false;
|
|
4153
4294
|
}
|
|
4295
|
+
if (ancestor.isLayerNode()) break;
|
|
4154
4296
|
ancestor = ancestor.parent instanceof Element ? ancestor.parent : void 0;
|
|
4155
4297
|
}
|
|
4156
4298
|
return true;
|
|
4157
4299
|
}
|
|
4158
4300
|
/** 判断查询区域与元素 bounds 的交集是否仍通过 ancestor clips。 */
|
|
4159
|
-
function hasAreaIntersection(element, area, boundsKind) {
|
|
4301
|
+
function hasAreaIntersection(root, element, area, boundsKind) {
|
|
4302
|
+
if (!isActiveVisibleNode(root, element)) return false;
|
|
4160
4303
|
const bounds = boundsKind === "visual" ? element.transform.getWorldVisualBoundsView() : element.transform.getHitTestBounds();
|
|
4161
4304
|
if (!Intersection.isAABBIntersect(area, bounds)) return false;
|
|
4162
4305
|
const left = Math.max(area.left, bounds.minX);
|
|
@@ -4183,6 +4326,7 @@ function hasAreaIntersection(element, area, boundsKind) {
|
|
|
4183
4326
|
];
|
|
4184
4327
|
let ancestor = element.parent instanceof Element ? element.parent : void 0;
|
|
4185
4328
|
while (ancestor) {
|
|
4329
|
+
if (!isActiveVisibleNode(root, ancestor)) return false;
|
|
4186
4330
|
const clip = ancestor.getChildClipRect();
|
|
4187
4331
|
if (clip) {
|
|
4188
4332
|
const localIntersection = [];
|
|
@@ -4195,6 +4339,7 @@ function hasAreaIntersection(element, area, boundsKind) {
|
|
|
4195
4339
|
if (clipped.length === 0) return false;
|
|
4196
4340
|
intersection = clipped.map((point) => ancestor.transform.toWorld(point));
|
|
4197
4341
|
}
|
|
4342
|
+
if (ancestor.isLayerNode()) break;
|
|
4198
4343
|
ancestor = ancestor.parent instanceof Element ? ancestor.parent : void 0;
|
|
4199
4344
|
}
|
|
4200
4345
|
return true;
|
|
@@ -4202,16 +4347,6 @@ function hasAreaIntersection(element, area, boundsKind) {
|
|
|
4202
4347
|
function isActiveVisibleNode(root, element) {
|
|
4203
4348
|
return element.isActive && !element.isUnmounted && element.activeRoot === root && element.visible;
|
|
4204
4349
|
}
|
|
4205
|
-
/** 判断元素及其祖先是否参与当前 Root 的区域选择。 */
|
|
4206
|
-
function isAreaTarget(root, element) {
|
|
4207
|
-
let node = element;
|
|
4208
|
-
while (node) {
|
|
4209
|
-
if (!isActiveVisibleNode(root, node)) return false;
|
|
4210
|
-
if (node.isLayerNode()) break;
|
|
4211
|
-
node = node.parent instanceof Element ? node.parent : void 0;
|
|
4212
|
-
}
|
|
4213
|
-
return true;
|
|
4214
|
-
}
|
|
4215
4350
|
/** 判断元素是否符合当前 Root 的 pointer target 语义。@internal */
|
|
4216
4351
|
function isPointerTarget(root, element) {
|
|
4217
4352
|
if (element.pickable === false) return false;
|
|
@@ -4248,8 +4383,7 @@ function* iteratePointElements(root, point, options = {}) {
|
|
|
4248
4383
|
if (!element.isActive || element.isUnmounted || element.activeRoot !== root || element.layer !== layer || root.getPaintOrder(element) === void 0) continue;
|
|
4249
4384
|
if (options.exclude?.has(element.key)) continue;
|
|
4250
4385
|
if (scope && !isStrictDescendant(element, scope)) continue;
|
|
4251
|
-
if (!
|
|
4252
|
-
if (!isPointWithinAncestorClips(element, point)) continue;
|
|
4386
|
+
if (!isPointerTargetWithinAncestorClips(root, element, point)) continue;
|
|
4253
4387
|
if (element.transform.hasPointHint(point)) yield element;
|
|
4254
4388
|
}
|
|
4255
4389
|
}
|
|
@@ -4338,7 +4472,7 @@ function queryAreaElements(root, area, options = {}) {
|
|
|
4338
4472
|
for (let index = root.layers.length - 1; index >= 0; index--) {
|
|
4339
4473
|
const hitElements = root.layers[index].queryCommittedElements(area, order);
|
|
4340
4474
|
for (const element of hitElements) {
|
|
4341
|
-
if (!
|
|
4475
|
+
if (!isSelectable(element) || !hasAreaIntersection(root, element, area, bounds)) continue;
|
|
4342
4476
|
result.push(element);
|
|
4343
4477
|
}
|
|
4344
4478
|
}
|
|
@@ -6805,4 +6939,4 @@ var Layer = class Layer extends Element {
|
|
|
6805
6939
|
}
|
|
6806
6940
|
};
|
|
6807
6941
|
//#endregion
|
|
6808
|
-
export { ChangeImpact, CustomEvent, DEFAULT_CONNECTION_PORT_SCHEMA, EVENT_NAMES, Element, ElementProperties, ElementTransform, EventEmitter, FrameStabilizationError, Layer, LayerProperties, LayerTransform, Node, NodeLifecycleEvent, NodeProperties, Root, RootFrameRuntime, RootInputController, SKIN_DEFAULTS, SceneRegistry, Shape, ShapeProperties, ShapeTransform, Skin, TransformProperties, Viewport, createAuthoredLineSubpath, createGeometryQuery, createGeometrySnapshot, createLineSubpath, createPathLocalId, evaluatePathSegmentPoint, getElementConnectionPort, getElementConnectionPortPoint, getElementConnectionPortPoints, isFormControlTarget, isReparentTransformIntent, projectConnectionPortPoint, resolveAuthoredSubpath, resolveOrigin, resolveReferenceTransform, resolveReferenceTransformTarget, splitPathSegment };
|
|
6942
|
+
export { ChangeImpact, CustomEvent, DEFAULT_CONNECTION_PORT_SCHEMA, EVENT_NAMES, Element, ElementProperties, ElementTransform, EventEmitter, FrameStabilizationError, Layer, LayerProperties, LayerTransform, Node, NodeLifecycleEvent, NodeProperties, Root, RootFrameRuntime, RootInputController, SKIN_DEFAULTS, SceneRegistry, Shape, ShapeProperties, ShapeTransform, Skin, TransformProperties, Viewport, createAuthoredLineSubpath, createGeometryQuery, createGeometrySnapshot, createLineSubpath, createPathLocalId, evaluatePathSegmentPoint, getElementConnectionPort, getElementConnectionPortPoint, getElementConnectionPortPoints, isFormControlTarget, isReparentTransformIntent, projectConnectionPortPoint, resolveAuthoredGeometryFramePatch, resolveAuthoredSubpath, resolveOrigin, resolveReferenceTransform, resolveReferenceTransformTarget, splitPathSegment };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Bound, type Rect } from "@fulate/share";
|
|
2
|
+
/** Project a local rectangle into a matrix space and return its AABB. */
|
|
3
|
+
export declare function projectBounds(matrix: DOMMatrixReadOnly, bounds: Readonly<Rect> | null): Bound;
|
|
4
|
+
/** Compare a committed matrix with a previous scalar matrix sample. */
|
|
5
|
+
export declare function matrixChanged(matrix: DOMMatrixReadOnly, previousA: number, previousB: number, previousC: number, previousD: number, previousE: number, previousF: number): boolean;
|
|
@@ -7,10 +7,11 @@ import { ChangeImpact } from "./properties";
|
|
|
7
7
|
export type GeometryBoundsView = Readonly<Rect & BoundingBox>;
|
|
8
8
|
/** 一次 Layer damage 收集共享的子树 visual bounds memo。 @internal */
|
|
9
9
|
export type DirtyBoundsMemo = Map<ElementTransform, Bound | null>;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Element 继承构造链内部使用的 companion constructor。默认参数保留
|
|
12
|
+
* existential 入口;具体 Element 子类可以同时声明 owner 与 Transform。
|
|
13
|
+
*/
|
|
14
|
+
export type ElementTransformClass<E extends Element = Element, T extends ElementTransform<any> = ElementTransform<any>> = new (element: E) => T;
|
|
14
15
|
/**
|
|
15
16
|
* 一个 Element 唯一的变换、几何、damage 与子树派生状态 owner。
|
|
16
17
|
*
|
|
@@ -191,6 +192,8 @@ export declare class ElementTransform<E extends Element = Element> {
|
|
|
191
192
|
clearPreviousVisualBounds(): void;
|
|
192
193
|
/** @internal 按影响范围失效几何并安排 Layer 提交。 */
|
|
193
194
|
invalidate(impact: ChangeImpact, previousBounds?: Readonly<BoundingBox> | null): void;
|
|
195
|
+
/** A derived layout can change visual bounds during an already pending commit. @internal */
|
|
196
|
+
invalidateLayoutBounds(): void;
|
|
194
197
|
/** 标记 local 几何需要重新构建。 */
|
|
195
198
|
markGeometryNeedsLayout(notifyScene?: boolean): void;
|
|
196
199
|
/** 安排当前元素所属 Layer 重绘。 @internal */
|
|
@@ -34,6 +34,11 @@ export interface TransformIntent {
|
|
|
34
34
|
readonly worldDelta: DOMMatrixReadOnly;
|
|
35
35
|
/** 连续旋转手势从 frozen baseline 累积的未折叠 world 角度。 */
|
|
36
36
|
readonly unwrappedRotationDelta?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Resize/flip 这类矢量编辑操作把线性变换写回 authored geometry,
|
|
39
|
+
* 而不是把它留在容器的 scale 上。普通 move/rotate 保持原有 pose owner。
|
|
40
|
+
*/
|
|
41
|
+
readonly bakeGeometry?: boolean;
|
|
37
42
|
}
|
|
38
43
|
/** 只有显式 reparent 才携带的新 parent world reference。 */
|
|
39
44
|
export interface ReparentTransformIntent extends TransformIntent {
|
|
@@ -50,6 +55,12 @@ export interface ReferenceTransformResolution {
|
|
|
50
55
|
readonly framePatch: Readonly<ElementOptionPatch>;
|
|
51
56
|
readonly authoredLocalDelta: DOMMatrixReadOnly;
|
|
52
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* 用 owner 已算出的精确 authored bounds 修正 bake frame 的位置。
|
|
60
|
+
* AABB 经过旋转/错切后不能由原 bounds 四角精确推出;owner 在生成
|
|
61
|
+
* 新几何后把真实 bounds 交回这里,保持 frame world matrix 与目标一致。
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveAuthoredGeometryFramePatch(baseline: Readonly<Pick<ReferenceTransformBaseline, "referenceBounds" | "frame">>, resolution: Readonly<ReferenceTransformResolution>, authoredBounds: Readonly<Rect>): Readonly<ElementOptionPatch>;
|
|
53
64
|
/** 判断变换意图是否包含跨父级重挂载所需的目标父级矩阵。 */
|
|
54
65
|
export declare function isReparentTransformIntent(intent: Readonly<TransformIntent | ReparentTransformIntent>): intent is Readonly<ReparentTransformIntent>;
|
|
55
66
|
/**
|