canvu-react 0.4.17 → 0.4.19
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/native.cjs +720 -125
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +20 -1
- package/dist/native.d.ts +20 -1
- package/dist/native.js +720 -125
- package/dist/native.js.map +1 -1
- package/package.json +1 -1
package/dist/native.cjs
CHANGED
|
@@ -946,6 +946,10 @@ function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
|
|
|
946
946
|
const ly = sin * dx + cos * dy;
|
|
947
947
|
return { x: c.x + lx, y: c.y + ly };
|
|
948
948
|
}
|
|
949
|
+
function itemPivotWorld(item) {
|
|
950
|
+
const r = normalizeRect(item.bounds);
|
|
951
|
+
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
952
|
+
}
|
|
949
953
|
function boundsAabbForRotatedItem(item) {
|
|
950
954
|
const rot = getItemRotationRad(item);
|
|
951
955
|
if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
|
|
@@ -976,6 +980,7 @@ function boundsAabbForRotatedItem(item) {
|
|
|
976
980
|
}
|
|
977
981
|
|
|
978
982
|
// src/interaction/resize-handles.ts
|
|
983
|
+
var HANDLE_IDS = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
|
|
979
984
|
function getHandleWorldPosition(bounds, id) {
|
|
980
985
|
const r = normalizeRect(bounds);
|
|
981
986
|
const cx = r.x + r.width / 2;
|
|
@@ -999,6 +1004,30 @@ function getHandleWorldPosition(bounds, id) {
|
|
|
999
1004
|
return { x: r.x, y: cy };
|
|
1000
1005
|
}
|
|
1001
1006
|
}
|
|
1007
|
+
function hitTestResizeHandle(bounds, worldX, worldY, radiusWorld, rotationRad = 0) {
|
|
1008
|
+
const r = normalizeRect(bounds);
|
|
1009
|
+
const pl = worldToItemLocal(
|
|
1010
|
+
worldX,
|
|
1011
|
+
worldY,
|
|
1012
|
+
r.x,
|
|
1013
|
+
r.y,
|
|
1014
|
+
r.width,
|
|
1015
|
+
r.height,
|
|
1016
|
+
rotationRad
|
|
1017
|
+
);
|
|
1018
|
+
const localBounds2 = { x: 0, y: 0, width: r.width, height: r.height };
|
|
1019
|
+
let best = null;
|
|
1020
|
+
let bestD = radiusWorld;
|
|
1021
|
+
for (const id of HANDLE_IDS) {
|
|
1022
|
+
const p = getHandleWorldPosition(localBounds2, id);
|
|
1023
|
+
const d = Math.hypot(pl.x - p.x, pl.y - p.y);
|
|
1024
|
+
if (d <= bestD) {
|
|
1025
|
+
bestD = d;
|
|
1026
|
+
best = id;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return best;
|
|
1030
|
+
}
|
|
1002
1031
|
function getHandleWorldPositionRotated(bounds, handle, rotationRad) {
|
|
1003
1032
|
const r = normalizeRect(bounds);
|
|
1004
1033
|
const p = getHandleWorldPosition(
|
|
@@ -1019,6 +1048,10 @@ function getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld)
|
|
|
1019
1048
|
rotationRad
|
|
1020
1049
|
);
|
|
1021
1050
|
}
|
|
1051
|
+
function hitTestRotateHandle(bounds, rotationRad, worldX, worldY, radiusWorld, handleOffsetWorld) {
|
|
1052
|
+
const p = getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld);
|
|
1053
|
+
return Math.hypot(worldX - p.x, worldY - p.y) <= radiusWorld;
|
|
1054
|
+
}
|
|
1022
1055
|
function rectFromCorners(a, b) {
|
|
1023
1056
|
const minX = Math.min(a.x, b.x);
|
|
1024
1057
|
const maxX = Math.max(a.x, b.x);
|
|
@@ -1026,6 +1059,155 @@ function rectFromCorners(a, b) {
|
|
|
1026
1059
|
const maxY = Math.max(a.y, b.y);
|
|
1027
1060
|
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
1028
1061
|
}
|
|
1062
|
+
function clampMinSize(r, min) {
|
|
1063
|
+
const n = normalizeRect(r);
|
|
1064
|
+
return {
|
|
1065
|
+
x: n.x,
|
|
1066
|
+
y: n.y,
|
|
1067
|
+
width: Math.max(min, n.width),
|
|
1068
|
+
height: Math.max(min, n.height)
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function computeResizeBounds(bounds, handle, currentWorld) {
|
|
1072
|
+
const r = normalizeRect(bounds);
|
|
1073
|
+
const x0 = r.x;
|
|
1074
|
+
const y0 = r.y;
|
|
1075
|
+
const x1 = r.x + r.width;
|
|
1076
|
+
const y1 = r.y + r.height;
|
|
1077
|
+
const minSize = 8;
|
|
1078
|
+
switch (handle) {
|
|
1079
|
+
case "nw":
|
|
1080
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x1, y: y1 }), minSize);
|
|
1081
|
+
case "ne":
|
|
1082
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x0, y: y1 }), minSize);
|
|
1083
|
+
case "se":
|
|
1084
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x0, y: y0 }), minSize);
|
|
1085
|
+
case "sw":
|
|
1086
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x1, y: y0 }), minSize);
|
|
1087
|
+
case "n":
|
|
1088
|
+
return clampMinSize(
|
|
1089
|
+
rectFromCorners({ x: x0, y: currentWorld.y }, { x: x1, y: y1 }),
|
|
1090
|
+
minSize
|
|
1091
|
+
);
|
|
1092
|
+
case "s":
|
|
1093
|
+
return clampMinSize(
|
|
1094
|
+
rectFromCorners({ x: x0, y: y0 }, { x: x1, y: currentWorld.y }),
|
|
1095
|
+
minSize
|
|
1096
|
+
);
|
|
1097
|
+
case "e":
|
|
1098
|
+
return clampMinSize(
|
|
1099
|
+
rectFromCorners({ x: x0, y: y0 }, { x: currentWorld.x, y: y1 }),
|
|
1100
|
+
minSize
|
|
1101
|
+
);
|
|
1102
|
+
case "w":
|
|
1103
|
+
return clampMinSize(
|
|
1104
|
+
rectFromCorners({ x: currentWorld.x, y: y0 }, { x: x1, y: y1 }),
|
|
1105
|
+
minSize
|
|
1106
|
+
);
|
|
1107
|
+
default:
|
|
1108
|
+
return r;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function computeResizeBoundsFixedAspect(bounds, handle, currentWorld, aspect) {
|
|
1112
|
+
const r = normalizeRect(bounds);
|
|
1113
|
+
const x0 = r.x;
|
|
1114
|
+
const y0 = r.y;
|
|
1115
|
+
const x1 = r.x + r.width;
|
|
1116
|
+
const y1 = r.y + r.height;
|
|
1117
|
+
const w = r.width;
|
|
1118
|
+
const h = r.height;
|
|
1119
|
+
const minSize = 8;
|
|
1120
|
+
const a = Math.max(1e-9, aspect);
|
|
1121
|
+
function cornerAspectRect(anchor, cursor) {
|
|
1122
|
+
const dx = cursor.x - anchor.x;
|
|
1123
|
+
const dy = cursor.y - anchor.y;
|
|
1124
|
+
const aw = Math.abs(dx);
|
|
1125
|
+
const ah = Math.abs(dy);
|
|
1126
|
+
let W;
|
|
1127
|
+
let H;
|
|
1128
|
+
if (aw < 1e-12 && ah < 1e-12) {
|
|
1129
|
+
W = minSize;
|
|
1130
|
+
H = minSize / a;
|
|
1131
|
+
} else if (ah < 1e-12) {
|
|
1132
|
+
W = Math.max(minSize, aw);
|
|
1133
|
+
H = W / a;
|
|
1134
|
+
} else if (aw < 1e-12) {
|
|
1135
|
+
H = Math.max(minSize, ah);
|
|
1136
|
+
W = H * a;
|
|
1137
|
+
} else if (aw / ah > a) {
|
|
1138
|
+
W = aw;
|
|
1139
|
+
H = aw / a;
|
|
1140
|
+
} else {
|
|
1141
|
+
H = ah;
|
|
1142
|
+
W = ah * a;
|
|
1143
|
+
}
|
|
1144
|
+
if (W < minSize || H < minSize) {
|
|
1145
|
+
const scale = Math.max(minSize / W, minSize / H);
|
|
1146
|
+
W *= scale;
|
|
1147
|
+
H *= scale;
|
|
1148
|
+
}
|
|
1149
|
+
const sx = Math.sign(dx) || 1;
|
|
1150
|
+
const sy = Math.sign(dy) || 1;
|
|
1151
|
+
return rectFromCorners(anchor, {
|
|
1152
|
+
x: anchor.x + sx * W,
|
|
1153
|
+
y: anchor.y + sy * H
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
switch (handle) {
|
|
1157
|
+
case "se":
|
|
1158
|
+
return cornerAspectRect({ x: x0, y: y0 }, currentWorld);
|
|
1159
|
+
case "nw":
|
|
1160
|
+
return cornerAspectRect({ x: x1, y: y1 }, currentWorld);
|
|
1161
|
+
case "ne":
|
|
1162
|
+
return cornerAspectRect({ x: x0, y: y1 }, currentWorld);
|
|
1163
|
+
case "sw":
|
|
1164
|
+
return cornerAspectRect({ x: x1, y: y0 }, currentWorld);
|
|
1165
|
+
case "e": {
|
|
1166
|
+
const rawW = currentWorld.x - x0;
|
|
1167
|
+
const aw = Math.max(minSize, Math.abs(rawW));
|
|
1168
|
+
const H = aw / a;
|
|
1169
|
+
const newY = y0 + h / 2 - H / 2;
|
|
1170
|
+
const sign = Math.sign(rawW) || 1;
|
|
1171
|
+
return normalizeRect({ x: x0, y: newY, width: sign * aw, height: H });
|
|
1172
|
+
}
|
|
1173
|
+
case "w": {
|
|
1174
|
+
const rawW = x1 - currentWorld.x;
|
|
1175
|
+
const aw = Math.max(minSize, Math.abs(rawW));
|
|
1176
|
+
const H = aw / a;
|
|
1177
|
+
const newY = y0 + h / 2 - H / 2;
|
|
1178
|
+
const sign = Math.sign(rawW) || 1;
|
|
1179
|
+
return normalizeRect({
|
|
1180
|
+
x: x1 - sign * aw,
|
|
1181
|
+
y: newY,
|
|
1182
|
+
width: sign * aw,
|
|
1183
|
+
height: H
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
case "n": {
|
|
1187
|
+
const rawH = y1 - currentWorld.y;
|
|
1188
|
+
const ah = Math.max(minSize, Math.abs(rawH));
|
|
1189
|
+
const W = ah * a;
|
|
1190
|
+
const newX = x0 + w / 2 - W / 2;
|
|
1191
|
+
const sign = Math.sign(rawH) || 1;
|
|
1192
|
+
return normalizeRect({
|
|
1193
|
+
x: newX,
|
|
1194
|
+
y: y1 - sign * ah,
|
|
1195
|
+
width: W,
|
|
1196
|
+
height: sign * ah
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
case "s": {
|
|
1200
|
+
const rawH = currentWorld.y - y0;
|
|
1201
|
+
const ah = Math.max(minSize, Math.abs(rawH));
|
|
1202
|
+
const W = ah * a;
|
|
1203
|
+
const newX = x0 + w / 2 - W / 2;
|
|
1204
|
+
const sign = Math.sign(rawH) || 1;
|
|
1205
|
+
return normalizeRect({ x: newX, y: y0, width: W, height: sign * ah });
|
|
1206
|
+
}
|
|
1207
|
+
default:
|
|
1208
|
+
return r;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1029
1211
|
|
|
1030
1212
|
// src/scene/freehand-path.ts
|
|
1031
1213
|
function smoothFreehandPointsToPathD(points) {
|
|
@@ -3587,6 +3769,211 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
|
|
|
3587
3769
|
return ids;
|
|
3588
3770
|
}
|
|
3589
3771
|
|
|
3772
|
+
// src/interaction/mutations.ts
|
|
3773
|
+
function computeNewBoundsForResize(item, sb, handle, currentWorld) {
|
|
3774
|
+
const rot = getItemRotationRad(item);
|
|
3775
|
+
if (Math.abs(rot) < 1e-12) {
|
|
3776
|
+
if (item.toolKind === "image") {
|
|
3777
|
+
let aspect;
|
|
3778
|
+
if (item.imageIntrinsicSize) {
|
|
3779
|
+
const iw = Math.max(1e-9, item.imageIntrinsicSize.width);
|
|
3780
|
+
const ih = Math.max(1e-9, item.imageIntrinsicSize.height);
|
|
3781
|
+
aspect = iw / ih;
|
|
3782
|
+
} else if (item.imageVectorLocalSize) {
|
|
3783
|
+
const lw = Math.max(1e-9, item.imageVectorLocalSize.width);
|
|
3784
|
+
const lh = Math.max(1e-9, item.imageVectorLocalSize.height);
|
|
3785
|
+
aspect = lw / lh;
|
|
3786
|
+
}
|
|
3787
|
+
return aspect !== void 0 ? computeResizeBoundsFixedAspect(sb, handle, currentWorld, aspect) : computeResizeBounds(sb, handle, currentWorld);
|
|
3788
|
+
}
|
|
3789
|
+
return computeResizeBounds(sb, handle, currentWorld);
|
|
3790
|
+
}
|
|
3791
|
+
const local = worldToItemLocal(
|
|
3792
|
+
currentWorld.x,
|
|
3793
|
+
currentWorld.y,
|
|
3794
|
+
sb.x,
|
|
3795
|
+
sb.y,
|
|
3796
|
+
sb.width,
|
|
3797
|
+
sb.height,
|
|
3798
|
+
rot
|
|
3799
|
+
);
|
|
3800
|
+
const localBounds2 = { x: 0, y: 0, width: sb.width, height: sb.height };
|
|
3801
|
+
let nbLocal;
|
|
3802
|
+
if (item.toolKind === "image") {
|
|
3803
|
+
let aspect;
|
|
3804
|
+
if (item.imageIntrinsicSize) {
|
|
3805
|
+
const iw = Math.max(1e-9, item.imageIntrinsicSize.width);
|
|
3806
|
+
const ih = Math.max(1e-9, item.imageIntrinsicSize.height);
|
|
3807
|
+
aspect = iw / ih;
|
|
3808
|
+
} else if (item.imageVectorLocalSize) {
|
|
3809
|
+
const lw = Math.max(1e-9, item.imageVectorLocalSize.width);
|
|
3810
|
+
const lh = Math.max(1e-9, item.imageVectorLocalSize.height);
|
|
3811
|
+
aspect = lw / lh;
|
|
3812
|
+
}
|
|
3813
|
+
nbLocal = aspect !== void 0 ? computeResizeBoundsFixedAspect(localBounds2, handle, local, aspect) : computeResizeBounds(localBounds2, handle, local);
|
|
3814
|
+
} else {
|
|
3815
|
+
nbLocal = computeResizeBounds(localBounds2, handle, local);
|
|
3816
|
+
}
|
|
3817
|
+
return {
|
|
3818
|
+
x: sb.x + nbLocal.x,
|
|
3819
|
+
y: sb.y + nbLocal.y,
|
|
3820
|
+
width: nbLocal.width,
|
|
3821
|
+
height: nbLocal.height
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
function applyRotationFromPointer(item, startRotation, startPointerAngleRad, pointerAngleRad) {
|
|
3825
|
+
let delta = pointerAngleRad - startPointerAngleRad;
|
|
3826
|
+
while (delta > Math.PI) {
|
|
3827
|
+
delta -= 2 * Math.PI;
|
|
3828
|
+
}
|
|
3829
|
+
while (delta < -Math.PI) {
|
|
3830
|
+
delta += 2 * Math.PI;
|
|
3831
|
+
}
|
|
3832
|
+
return { ...item, rotation: startRotation + delta };
|
|
3833
|
+
}
|
|
3834
|
+
function resizeItemByHandle(item, start, handle, currentWorld) {
|
|
3835
|
+
const sb = normalizeRect(start.bounds);
|
|
3836
|
+
const newBoundsRaw = computeNewBoundsForResize(item, sb, handle, currentWorld);
|
|
3837
|
+
const nb = normalizeRect(newBoundsRaw);
|
|
3838
|
+
const k = item.toolKind;
|
|
3839
|
+
if (k === "rect") {
|
|
3840
|
+
const style = resolveStrokeStyle(item);
|
|
3841
|
+
return {
|
|
3842
|
+
...item,
|
|
3843
|
+
x: nb.x,
|
|
3844
|
+
y: nb.y,
|
|
3845
|
+
bounds: nb,
|
|
3846
|
+
childrenSvg: buildRectSvg(nb.width, nb.height, style)
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
if (k === "ellipse") {
|
|
3850
|
+
const style = resolveStrokeStyle(item);
|
|
3851
|
+
return {
|
|
3852
|
+
...item,
|
|
3853
|
+
x: nb.x,
|
|
3854
|
+
y: nb.y,
|
|
3855
|
+
bounds: nb,
|
|
3856
|
+
childrenSvg: buildEllipseSvg(nb.width, nb.height, style)
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
if (k === "architectural-cloud") {
|
|
3860
|
+
const style = resolveStrokeStyle(item);
|
|
3861
|
+
return {
|
|
3862
|
+
...item,
|
|
3863
|
+
x: nb.x,
|
|
3864
|
+
y: nb.y,
|
|
3865
|
+
bounds: nb,
|
|
3866
|
+
childrenSvg: buildArchitecturalCloudSvg(nb.width, nb.height, style)
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
if (k === "text" && item.text !== void 0) {
|
|
3870
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3871
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3872
|
+
const baseFs = item.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
|
|
3873
|
+
const areaRatio = nb.width * nb.height / (sfw * sfh);
|
|
3874
|
+
const scale = Math.sqrt(Math.max(areaRatio, 1e-12));
|
|
3875
|
+
const nextFs = Math.min(256, Math.max(6, baseFs * scale));
|
|
3876
|
+
return rebuildItemSvg({
|
|
3877
|
+
...item,
|
|
3878
|
+
x: nb.x,
|
|
3879
|
+
y: nb.y,
|
|
3880
|
+
bounds: nb,
|
|
3881
|
+
textFixedBounds: true,
|
|
3882
|
+
textFontSize: nextFs
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
if (k === "image") {
|
|
3886
|
+
if (item.imageRasterHref && item.imageIntrinsicSize) {
|
|
3887
|
+
return {
|
|
3888
|
+
...item,
|
|
3889
|
+
x: nb.x,
|
|
3890
|
+
y: nb.y,
|
|
3891
|
+
bounds: nb,
|
|
3892
|
+
childrenSvg: buildRasterImageChildrenSvg(
|
|
3893
|
+
item.imageRasterHref,
|
|
3894
|
+
item.imageIntrinsicSize,
|
|
3895
|
+
nb
|
|
3896
|
+
)
|
|
3897
|
+
};
|
|
3898
|
+
}
|
|
3899
|
+
if (item.imageVectorInnerSvg && item.imageVectorLocalSize) {
|
|
3900
|
+
const lw = Math.max(1e-6, item.imageVectorLocalSize.width);
|
|
3901
|
+
const lh = Math.max(1e-6, item.imageVectorLocalSize.height);
|
|
3902
|
+
const arB = nb.width / Math.max(1e-9, nb.height);
|
|
3903
|
+
const arI = lw / lh;
|
|
3904
|
+
let childrenSvg;
|
|
3905
|
+
if (Math.abs(arB - arI) < 1e-3) {
|
|
3906
|
+
const s = nb.width / lw;
|
|
3907
|
+
childrenSvg = `<g transform="scale(${s})">${item.imageVectorInnerSvg}</g>`;
|
|
3908
|
+
} else {
|
|
3909
|
+
const s = Math.min(nb.width / lw, nb.height / lh);
|
|
3910
|
+
const tx = (nb.width - lw * s) / 2;
|
|
3911
|
+
const ty = (nb.height - lh * s) / 2;
|
|
3912
|
+
childrenSvg = `<g transform="translate(${tx}, ${ty}) scale(${s})">${item.imageVectorInnerSvg}</g>`;
|
|
3913
|
+
}
|
|
3914
|
+
return {
|
|
3915
|
+
...item,
|
|
3916
|
+
x: nb.x,
|
|
3917
|
+
y: nb.y,
|
|
3918
|
+
bounds: nb,
|
|
3919
|
+
childrenSvg
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
if ((k === "line" || k === "arrow") && start.line) {
|
|
3924
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3925
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3926
|
+
const f = start.line;
|
|
3927
|
+
const u1 = f.x1 / sfw;
|
|
3928
|
+
const v1 = f.y1 / sfh;
|
|
3929
|
+
const u2 = f.x2 / sfw;
|
|
3930
|
+
const v2 = f.y2 / sfh;
|
|
3931
|
+
const newLine = {
|
|
3932
|
+
x1: u1 * nb.width,
|
|
3933
|
+
y1: v1 * nb.height,
|
|
3934
|
+
x2: u2 * nb.width,
|
|
3935
|
+
y2: v2 * nb.height
|
|
3936
|
+
};
|
|
3937
|
+
const style = resolveStrokeStyle(item);
|
|
3938
|
+
const childrenSvg = k === "arrow" ? buildArrowSvg(item.id, newLine, style) : buildLineSvg(newLine, style);
|
|
3939
|
+
return {
|
|
3940
|
+
...item,
|
|
3941
|
+
x: nb.x,
|
|
3942
|
+
y: nb.y,
|
|
3943
|
+
bounds: nb,
|
|
3944
|
+
line: newLine,
|
|
3945
|
+
childrenSvg
|
|
3946
|
+
};
|
|
3947
|
+
}
|
|
3948
|
+
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
3949
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3950
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3951
|
+
const sx = nb.width / sfw;
|
|
3952
|
+
const sy = nb.height / sfh;
|
|
3953
|
+
const scaledPoints = item.pathPointsLocal.map((p) => ({
|
|
3954
|
+
x: p.x * sx,
|
|
3955
|
+
y: p.y * sy,
|
|
3956
|
+
...p.pressure != null ? { pressure: p.pressure } : {}
|
|
3957
|
+
}));
|
|
3958
|
+
return rebuildItemSvg({
|
|
3959
|
+
...item,
|
|
3960
|
+
x: nb.x,
|
|
3961
|
+
y: nb.y,
|
|
3962
|
+
bounds: nb,
|
|
3963
|
+
pathPointsLocal: scaledPoints
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
if (k === "custom" && item.customIntrinsicSize && item.customInnerSvg) {
|
|
3967
|
+
return rebuildItemSvg({
|
|
3968
|
+
...item,
|
|
3969
|
+
x: nb.x,
|
|
3970
|
+
y: nb.y,
|
|
3971
|
+
bounds: nb
|
|
3972
|
+
});
|
|
3973
|
+
}
|
|
3974
|
+
return { ...item, x: nb.x, y: nb.y, bounds: nb };
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3590
3977
|
// src/native/native-tool-cursors.ts
|
|
3591
3978
|
var ICON_SIZE = 24;
|
|
3592
3979
|
var CENTER_HOTSPOT = { x: 12, y: 12 };
|
|
@@ -3612,10 +3999,73 @@ function nativeCursorForVectorToolId(toolId) {
|
|
|
3612
3999
|
return null;
|
|
3613
4000
|
}
|
|
3614
4001
|
}
|
|
4002
|
+
function nativeCrosshairToolCursor() {
|
|
4003
|
+
return { kind: "crosshair", size: ICON_SIZE, hotspot: CENTER_HOTSPOT };
|
|
4004
|
+
}
|
|
3615
4005
|
function nativeFallbackToolCursorPoint(size) {
|
|
3616
4006
|
if (size.width <= 0 || size.height <= 0) return null;
|
|
3617
4007
|
return { x: size.width / 2, y: size.height / 2 };
|
|
3618
4008
|
}
|
|
4009
|
+
|
|
4010
|
+
// src/native/native-vector-interactions.ts
|
|
4011
|
+
var NATIVE_SELECTION_HANDLE_HIT_RADIUS_PX = 24;
|
|
4012
|
+
function supportsNativeResizeHandles(item) {
|
|
4013
|
+
const k = item?.toolKind;
|
|
4014
|
+
if (k === "rect" || k === "ellipse" || k === "architectural-cloud" || k === "line" || k === "arrow" || k === "image" || k === "text") {
|
|
4015
|
+
return true;
|
|
4016
|
+
}
|
|
4017
|
+
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item?.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
4018
|
+
return true;
|
|
4019
|
+
}
|
|
4020
|
+
return k === "custom" && !!item?.customIntrinsicSize && !!item?.customInnerSvg;
|
|
4021
|
+
}
|
|
4022
|
+
function hitTestNativeSelectionHandle({
|
|
4023
|
+
selectedItem,
|
|
4024
|
+
selectedCount,
|
|
4025
|
+
worldPoint,
|
|
4026
|
+
zoom
|
|
4027
|
+
}) {
|
|
4028
|
+
if (selectedCount !== 1) return null;
|
|
4029
|
+
if (!selectedItem || selectedItem.locked) return null;
|
|
4030
|
+
if (!supportsNativeResizeHandles(selectedItem)) return null;
|
|
4031
|
+
const bounds = normalizeRect(selectedItem.bounds);
|
|
4032
|
+
const rotation = selectedItem.rotation ?? 0;
|
|
4033
|
+
const handleRadiusWorld = NATIVE_SELECTION_HANDLE_HIT_RADIUS_PX / Math.max(zoom, 1e-9);
|
|
4034
|
+
const rotateOffsetWorld = 24 / Math.max(zoom, 1e-9);
|
|
4035
|
+
if (hitTestRotateHandle(
|
|
4036
|
+
bounds,
|
|
4037
|
+
rotation,
|
|
4038
|
+
worldPoint.x,
|
|
4039
|
+
worldPoint.y,
|
|
4040
|
+
handleRadiusWorld,
|
|
4041
|
+
rotateOffsetWorld
|
|
4042
|
+
)) {
|
|
4043
|
+
return { kind: "rotate" };
|
|
4044
|
+
}
|
|
4045
|
+
const handle = hitTestResizeHandle(
|
|
4046
|
+
bounds,
|
|
4047
|
+
worldPoint.x,
|
|
4048
|
+
worldPoint.y,
|
|
4049
|
+
handleRadiusWorld,
|
|
4050
|
+
rotation
|
|
4051
|
+
);
|
|
4052
|
+
return handle ? { kind: "resize", handle } : null;
|
|
4053
|
+
}
|
|
4054
|
+
function resolveNativeCustomPlacement(toolId, customPlacement, customPlacements) {
|
|
4055
|
+
if (customPlacement?.toolId === toolId) return customPlacement;
|
|
4056
|
+
return customPlacements?.find((placement) => placement.toolId === toolId) ?? null;
|
|
4057
|
+
}
|
|
4058
|
+
function nativeRotationDragStart(input) {
|
|
4059
|
+
const pivotWorld = itemPivotWorld(input.item);
|
|
4060
|
+
return {
|
|
4061
|
+
pivotWorld,
|
|
4062
|
+
startPointerAngleRad: Math.atan2(
|
|
4063
|
+
input.worldPoint.y - pivotWorld.y,
|
|
4064
|
+
input.worldPoint.x - pivotWorld.x
|
|
4065
|
+
),
|
|
4066
|
+
startRotation: input.item.rotation ?? 0
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
3619
4069
|
var MIN_PLACE_SIZE = 8;
|
|
3620
4070
|
var MIN_ARROW_DRAG_PX = 8;
|
|
3621
4071
|
var TAP_PX = 20;
|
|
@@ -3630,16 +4080,6 @@ function isPlacementTool(toolId) {
|
|
|
3630
4080
|
function isDefaultMarkerToolStyle(style) {
|
|
3631
4081
|
return style.stroke === MARKER_TOOL_STYLE.stroke && style.strokeWidth === MARKER_TOOL_STYLE.strokeWidth && style.strokeOpacity === MARKER_TOOL_STYLE.strokeOpacity;
|
|
3632
4082
|
}
|
|
3633
|
-
function supportsNativeResizeHandles(item) {
|
|
3634
|
-
const k = item?.toolKind;
|
|
3635
|
-
if (k === "rect" || k === "ellipse" || k === "architectural-cloud" || k === "line" || k === "arrow" || k === "image" || k === "text") {
|
|
3636
|
-
return true;
|
|
3637
|
-
}
|
|
3638
|
-
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item?.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
3639
|
-
return true;
|
|
3640
|
-
}
|
|
3641
|
-
return k === "custom" && !!item?.customIntrinsicSize && !!item?.customInnerSvg;
|
|
3642
|
-
}
|
|
3643
4083
|
function placementPreviewForTool(toolId, start, end) {
|
|
3644
4084
|
if (toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud") {
|
|
3645
4085
|
return { kind: toolId, rect: rectFromCorners(start, end) };
|
|
@@ -3707,7 +4147,11 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3707
4147
|
onSelectionChange,
|
|
3708
4148
|
onItemsChange,
|
|
3709
4149
|
onToolChangeRequest,
|
|
4150
|
+
onWorldPointerDown,
|
|
3710
4151
|
onCameraChange,
|
|
4152
|
+
customPlacement,
|
|
4153
|
+
customPlacements = [],
|
|
4154
|
+
customCrosshairToolIds = [],
|
|
3711
4155
|
toolbar,
|
|
3712
4156
|
showStyleInspector = false,
|
|
3713
4157
|
styleInspectorPlacement = "bottom"
|
|
@@ -3720,10 +4164,18 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3720
4164
|
toolLockedRef.current = toolLocked;
|
|
3721
4165
|
const onToolChangeRequestRef = react.useRef(onToolChangeRequest);
|
|
3722
4166
|
onToolChangeRequestRef.current = onToolChangeRequest;
|
|
4167
|
+
const onWorldPointerDownRef = react.useRef(onWorldPointerDown);
|
|
4168
|
+
onWorldPointerDownRef.current = onWorldPointerDown;
|
|
3723
4169
|
const onCameraChangeRef = react.useRef(onCameraChange);
|
|
3724
4170
|
onCameraChangeRef.current = onCameraChange;
|
|
3725
4171
|
const onItemsChangeRef = react.useRef(onItemsChange);
|
|
3726
4172
|
onItemsChangeRef.current = onItemsChange;
|
|
4173
|
+
const customPlacementRef = react.useRef(customPlacement);
|
|
4174
|
+
customPlacementRef.current = customPlacement;
|
|
4175
|
+
const customPlacementsRef = react.useRef(customPlacements);
|
|
4176
|
+
customPlacementsRef.current = customPlacements;
|
|
4177
|
+
const customCrosshairToolIdsRef = react.useRef(customCrosshairToolIds);
|
|
4178
|
+
customCrosshairToolIdsRef.current = customCrosshairToolIds;
|
|
3727
4179
|
const onSelectionChangeRef = react.useRef(onSelectionChange);
|
|
3728
4180
|
onSelectionChangeRef.current = onSelectionChange;
|
|
3729
4181
|
const itemsRef = react.useRef(items);
|
|
@@ -3831,6 +4283,11 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3831
4283
|
setCameraTick((n) => n + 1);
|
|
3832
4284
|
onCameraChangeRef.current?.();
|
|
3833
4285
|
}, []);
|
|
4286
|
+
const cursorForToolId = react.useCallback((nextToolId) => {
|
|
4287
|
+
const builtInCursor = nativeCursorForVectorToolId(nextToolId);
|
|
4288
|
+
if (builtInCursor) return builtInCursor;
|
|
4289
|
+
return customCrosshairToolIdsRef.current.includes(nextToolId) ? nativeCrosshairToolCursor() : null;
|
|
4290
|
+
}, []);
|
|
3834
4291
|
const onLayout = react.useCallback(
|
|
3835
4292
|
(e) => {
|
|
3836
4293
|
const { width, height } = e.nativeEvent.layout;
|
|
@@ -3840,7 +4297,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3840
4297
|
setToolCursorPoint(null);
|
|
3841
4298
|
return;
|
|
3842
4299
|
}
|
|
3843
|
-
if (!
|
|
4300
|
+
if (!cursorForToolId(toolIdRef.current)) {
|
|
3844
4301
|
setToolCursorPoint(null);
|
|
3845
4302
|
return;
|
|
3846
4303
|
}
|
|
@@ -3848,15 +4305,15 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3848
4305
|
(current) => current ?? nativeFallbackToolCursorPoint(nextSize)
|
|
3849
4306
|
);
|
|
3850
4307
|
},
|
|
3851
|
-
[interactive]
|
|
4308
|
+
[cursorForToolId, interactive]
|
|
3852
4309
|
);
|
|
3853
4310
|
const updateToolCursorPoint = react.useCallback(
|
|
3854
4311
|
(point) => {
|
|
3855
4312
|
if (!interactive) return;
|
|
3856
|
-
if (!
|
|
4313
|
+
if (!cursorForToolId(toolIdRef.current)) return;
|
|
3857
4314
|
setToolCursorPoint(point);
|
|
3858
4315
|
},
|
|
3859
|
-
[interactive]
|
|
4316
|
+
[cursorForToolId, interactive]
|
|
3860
4317
|
);
|
|
3861
4318
|
const hideToolCursor = react.useCallback(() => {
|
|
3862
4319
|
setToolCursorPoint(null);
|
|
@@ -3867,7 +4324,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3867
4324
|
setToolCursorPoint(null);
|
|
3868
4325
|
return;
|
|
3869
4326
|
}
|
|
3870
|
-
if (!
|
|
4327
|
+
if (!cursorForToolId(nextToolId)) {
|
|
3871
4328
|
setToolCursorPoint(null);
|
|
3872
4329
|
return;
|
|
3873
4330
|
}
|
|
@@ -3875,17 +4332,11 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3875
4332
|
(current) => current ?? nativeFallbackToolCursorPoint(size)
|
|
3876
4333
|
);
|
|
3877
4334
|
},
|
|
3878
|
-
[interactive, size]
|
|
4335
|
+
[cursorForToolId, interactive, size]
|
|
3879
4336
|
);
|
|
3880
4337
|
react.useEffect(() => {
|
|
3881
4338
|
showFallbackToolCursor(toolId);
|
|
3882
4339
|
}, [showFallbackToolCursor, toolId]);
|
|
3883
|
-
const handlePointerMove = react.useCallback(
|
|
3884
|
-
(event) => {
|
|
3885
|
-
updateToolCursorPoint(screenPointFromPointerEvent(event));
|
|
3886
|
-
},
|
|
3887
|
-
[updateToolCursorPoint]
|
|
3888
|
-
);
|
|
3889
4340
|
const selectedItems = react.useMemo(
|
|
3890
4341
|
() => items.filter((it) => selectedIds.includes(it.id)),
|
|
3891
4342
|
[items, selectedIds]
|
|
@@ -3898,6 +4349,153 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3898
4349
|
const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked && supportsNativeResizeHandles(selectedItems[0]);
|
|
3899
4350
|
const lastPinchDist = react.useRef(null);
|
|
3900
4351
|
const lastPanPoint = react.useRef(null);
|
|
4352
|
+
const applyDragMoveAtScreenPoint = react.useCallback(
|
|
4353
|
+
(point, pagePoint) => {
|
|
4354
|
+
const cam = cameraRef.current;
|
|
4355
|
+
if (!cam) return;
|
|
4356
|
+
updateToolCursorPoint(point);
|
|
4357
|
+
const { worldX, worldY } = screenToWorld(point.x, point.y);
|
|
4358
|
+
const st = dragStateRef.current;
|
|
4359
|
+
if (st.kind === "pan") {
|
|
4360
|
+
const current = pagePoint ?? point;
|
|
4361
|
+
if (lastPanPoint.current) {
|
|
4362
|
+
const dx = current.x - lastPanPoint.current.x;
|
|
4363
|
+
const dy = current.y - lastPanPoint.current.y;
|
|
4364
|
+
cam.x += dx;
|
|
4365
|
+
cam.y += dy;
|
|
4366
|
+
requestRender();
|
|
4367
|
+
}
|
|
4368
|
+
lastPanPoint.current = current;
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
lastPanPoint.current = null;
|
|
4372
|
+
if (st.kind === "draw") {
|
|
4373
|
+
const pts = st.points;
|
|
4374
|
+
const last = pts[pts.length - 1];
|
|
4375
|
+
const dx = worldX - (last?.x ?? worldX);
|
|
4376
|
+
const dy = worldY - (last?.y ?? worldY);
|
|
4377
|
+
const shouldAppendPoint = Math.hypot(dx, dy) > 0.5 / cam.zoom;
|
|
4378
|
+
if (shouldAppendPoint) {
|
|
4379
|
+
pts.push({ x: worldX, y: worldY });
|
|
4380
|
+
}
|
|
4381
|
+
if (st.tool === "laser") {
|
|
4382
|
+
if (shouldAppendPoint) {
|
|
4383
|
+
setLaserTrail((prev) => [
|
|
4384
|
+
...prev,
|
|
4385
|
+
{ x: worldX, y: worldY, t: Date.now() }
|
|
4386
|
+
]);
|
|
4387
|
+
}
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
setPlacementPreview({
|
|
4391
|
+
kind: "stroke",
|
|
4392
|
+
tool: st.tool,
|
|
4393
|
+
points: [...pts],
|
|
4394
|
+
style: { ...strokeStyleRef.current }
|
|
4395
|
+
});
|
|
4396
|
+
return;
|
|
4397
|
+
}
|
|
4398
|
+
if (st.kind === "move") {
|
|
4399
|
+
const dx = worldX - st.startWorld.x;
|
|
4400
|
+
const dy = worldY - st.startWorld.y;
|
|
4401
|
+
const change = onItemsChangeRef.current;
|
|
4402
|
+
if (!change) return;
|
|
4403
|
+
const nextList = itemsRef.current.map((it) => {
|
|
4404
|
+
const snap = st.snapshots[it.id];
|
|
4405
|
+
if (!snap) return it;
|
|
4406
|
+
return {
|
|
4407
|
+
...snap,
|
|
4408
|
+
x: snap.x + dx,
|
|
4409
|
+
y: snap.y + dy,
|
|
4410
|
+
bounds: {
|
|
4411
|
+
...snap.bounds,
|
|
4412
|
+
x: snap.bounds.x + dx,
|
|
4413
|
+
y: snap.bounds.y + dy
|
|
4414
|
+
}
|
|
4415
|
+
};
|
|
4416
|
+
});
|
|
4417
|
+
change(nextList);
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
4420
|
+
if (st.kind === "rotate") {
|
|
4421
|
+
const change = onItemsChangeRef.current;
|
|
4422
|
+
if (!change) return;
|
|
4423
|
+
const angle = Math.atan2(worldY - st.pivotWorld.y, worldX - st.pivotWorld.x);
|
|
4424
|
+
const next = applyRotationFromPointer(
|
|
4425
|
+
st.snapshot,
|
|
4426
|
+
st.startRotation,
|
|
4427
|
+
st.startPointerAngleRad,
|
|
4428
|
+
angle
|
|
4429
|
+
);
|
|
4430
|
+
change(itemsRef.current.map((item) => item.id === st.id ? next : item));
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4433
|
+
if (st.kind === "resize") {
|
|
4434
|
+
const change = onItemsChangeRef.current;
|
|
4435
|
+
if (!change) return;
|
|
4436
|
+
const next = resizeItemByHandle(st.snapshot, st.start, st.handle, {
|
|
4437
|
+
x: worldX,
|
|
4438
|
+
y: worldY
|
|
4439
|
+
});
|
|
4440
|
+
change(itemsRef.current.map((item) => item.id === st.id ? next : item));
|
|
4441
|
+
return;
|
|
4442
|
+
}
|
|
4443
|
+
if (st.kind === "marquee") {
|
|
4444
|
+
const a = st.startWorld;
|
|
4445
|
+
const b = { x: worldX, y: worldY };
|
|
4446
|
+
const rect = {
|
|
4447
|
+
x: Math.min(a.x, b.x),
|
|
4448
|
+
y: Math.min(a.y, b.y),
|
|
4449
|
+
width: Math.abs(b.x - a.x),
|
|
4450
|
+
height: Math.abs(b.y - a.y)
|
|
4451
|
+
};
|
|
4452
|
+
setPlacementPreview({ kind: "marquee", rect });
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
if (st.kind === "erase") {
|
|
4456
|
+
setEraserTrail((prev) => [...prev, { x: worldX, y: worldY, t: Date.now() }]);
|
|
4457
|
+
const toErase = collectEraserTargetsAtWorldPoint(
|
|
4458
|
+
itemsRef.current,
|
|
4459
|
+
worldX,
|
|
4460
|
+
worldY,
|
|
4461
|
+
{ lineHitWorld: 10 / cam.zoom, ignoreLocked: true }
|
|
4462
|
+
);
|
|
4463
|
+
for (const id of toErase) {
|
|
4464
|
+
eraserPreviewIdSetRef.current.add(id);
|
|
4465
|
+
}
|
|
4466
|
+
setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
|
|
4467
|
+
return;
|
|
4468
|
+
}
|
|
4469
|
+
if (st.kind === "place") {
|
|
4470
|
+
setPlacementPreview(
|
|
4471
|
+
placementPreviewForTool(st.tool, st.startWorld, {
|
|
4472
|
+
x: worldX,
|
|
4473
|
+
y: worldY
|
|
4474
|
+
})
|
|
4475
|
+
);
|
|
4476
|
+
return;
|
|
4477
|
+
}
|
|
4478
|
+
if (st.kind === "custom-place") {
|
|
4479
|
+
setPlacementPreview({
|
|
4480
|
+
kind: "rect",
|
|
4481
|
+
rect: rectFromCorners(st.startWorld, { x: worldX, y: worldY })
|
|
4482
|
+
});
|
|
4483
|
+
return;
|
|
4484
|
+
}
|
|
4485
|
+
},
|
|
4486
|
+
[requestRender, screenToWorld, updateToolCursorPoint]
|
|
4487
|
+
);
|
|
4488
|
+
const handlePointerMove = react.useCallback(
|
|
4489
|
+
(event) => {
|
|
4490
|
+
const point = screenPointFromPointerEvent(event);
|
|
4491
|
+
if (dragStateRef.current.kind !== "idle") {
|
|
4492
|
+
applyDragMoveAtScreenPoint(point, point);
|
|
4493
|
+
return;
|
|
4494
|
+
}
|
|
4495
|
+
updateToolCursorPoint(point);
|
|
4496
|
+
},
|
|
4497
|
+
[applyDragMoveAtScreenPoint, updateToolCursorPoint]
|
|
4498
|
+
);
|
|
3901
4499
|
const panResponder = react.useMemo(
|
|
3902
4500
|
() => reactNative.PanResponder.create({
|
|
3903
4501
|
onStartShouldSetPanResponder: () => true,
|
|
@@ -3927,6 +4525,40 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3927
4525
|
return;
|
|
3928
4526
|
}
|
|
3929
4527
|
if (tool === "select") {
|
|
4528
|
+
const currentSelectedIds = selectedIdsRef.current;
|
|
4529
|
+
const selectedItem = currentSelectedIds.length === 1 ? itemsRef.current.find((item) => item.id === currentSelectedIds[0]) : void 0;
|
|
4530
|
+
const selectionHandle = hitTestNativeSelectionHandle({
|
|
4531
|
+
selectedItem,
|
|
4532
|
+
selectedCount: currentSelectedIds.length,
|
|
4533
|
+
worldPoint: { x: worldX, y: worldY },
|
|
4534
|
+
zoom: cam.zoom
|
|
4535
|
+
});
|
|
4536
|
+
if (selectionHandle && selectedItem) {
|
|
4537
|
+
if (selectionHandle.kind === "rotate") {
|
|
4538
|
+
const rotationStart = nativeRotationDragStart({
|
|
4539
|
+
item: selectedItem,
|
|
4540
|
+
worldPoint: { x: worldX, y: worldY }
|
|
4541
|
+
});
|
|
4542
|
+
dragStateRef.current = {
|
|
4543
|
+
kind: "rotate",
|
|
4544
|
+
id: selectedItem.id,
|
|
4545
|
+
snapshot: selectedItem,
|
|
4546
|
+
...rotationStart
|
|
4547
|
+
};
|
|
4548
|
+
return;
|
|
4549
|
+
}
|
|
4550
|
+
dragStateRef.current = {
|
|
4551
|
+
kind: "resize",
|
|
4552
|
+
id: selectedItem.id,
|
|
4553
|
+
handle: selectionHandle.handle,
|
|
4554
|
+
snapshot: selectedItem,
|
|
4555
|
+
start: {
|
|
4556
|
+
bounds: selectedItem.bounds,
|
|
4557
|
+
line: selectedItem.line
|
|
4558
|
+
}
|
|
4559
|
+
};
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
3930
4562
|
const hit = hitTestWorldPoint(itemsRef.current, worldX, worldY, {
|
|
3931
4563
|
lineHitWorld: 10 / cam.zoom,
|
|
3932
4564
|
ignoreLocked: true
|
|
@@ -4019,6 +4651,25 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4019
4651
|
);
|
|
4020
4652
|
return;
|
|
4021
4653
|
}
|
|
4654
|
+
const customPlacement2 = resolveNativeCustomPlacement(
|
|
4655
|
+
tool,
|
|
4656
|
+
customPlacementRef.current,
|
|
4657
|
+
customPlacementsRef.current
|
|
4658
|
+
);
|
|
4659
|
+
if (customPlacement2) {
|
|
4660
|
+
dragStateRef.current = {
|
|
4661
|
+
kind: "custom-place",
|
|
4662
|
+
tool,
|
|
4663
|
+
placement: customPlacement2,
|
|
4664
|
+
startWorld: { x: worldX, y: worldY },
|
|
4665
|
+
startScreen: { x: sx, y: sy }
|
|
4666
|
+
};
|
|
4667
|
+
setPlacementPreview({
|
|
4668
|
+
kind: "rect",
|
|
4669
|
+
rect: { x: worldX, y: worldY, width: 0, height: 0 }
|
|
4670
|
+
});
|
|
4671
|
+
return;
|
|
4672
|
+
}
|
|
4022
4673
|
if (tool === "note" || tool === "text") {
|
|
4023
4674
|
dragStateRef.current = {
|
|
4024
4675
|
kind: "tap",
|
|
@@ -4028,6 +4679,18 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4028
4679
|
};
|
|
4029
4680
|
return;
|
|
4030
4681
|
}
|
|
4682
|
+
const handleWorldPointerDown = onWorldPointerDownRef.current;
|
|
4683
|
+
if (handleWorldPointerDown) {
|
|
4684
|
+
handleWorldPointerDown({
|
|
4685
|
+
toolId: tool,
|
|
4686
|
+
worldX,
|
|
4687
|
+
worldY,
|
|
4688
|
+
screenX: sx,
|
|
4689
|
+
screenY: sy
|
|
4690
|
+
});
|
|
4691
|
+
requestSelectToolAfterUse();
|
|
4692
|
+
return;
|
|
4693
|
+
}
|
|
4031
4694
|
dragStateRef.current = { kind: "pan" };
|
|
4032
4695
|
},
|
|
4033
4696
|
onPanResponderMove: (evt) => {
|
|
@@ -4059,108 +4722,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4059
4722
|
return;
|
|
4060
4723
|
}
|
|
4061
4724
|
lastPinchDist.current = null;
|
|
4062
|
-
|
|
4063
|
-
const { worldX, worldY } = screenToWorld(sx, sy);
|
|
4064
|
-
const st = dragStateRef.current;
|
|
4065
|
-
if (st.kind === "pan") {
|
|
4066
|
-
const current = { x: pageX, y: pageY };
|
|
4067
|
-
if (lastPanPoint.current) {
|
|
4068
|
-
const dx = current.x - lastPanPoint.current.x;
|
|
4069
|
-
const dy = current.y - lastPanPoint.current.y;
|
|
4070
|
-
cam.x += dx;
|
|
4071
|
-
cam.y += dy;
|
|
4072
|
-
requestRender();
|
|
4073
|
-
}
|
|
4074
|
-
lastPanPoint.current = current;
|
|
4075
|
-
return;
|
|
4076
|
-
}
|
|
4077
|
-
lastPanPoint.current = null;
|
|
4078
|
-
if (st.kind === "draw") {
|
|
4079
|
-
const pts = st.points;
|
|
4080
|
-
const last = pts[pts.length - 1];
|
|
4081
|
-
const dx = worldX - (last?.x ?? worldX);
|
|
4082
|
-
const dy = worldY - (last?.y ?? worldY);
|
|
4083
|
-
const shouldAppendPoint = Math.hypot(dx, dy) > 0.5 / cam.zoom;
|
|
4084
|
-
if (shouldAppendPoint) {
|
|
4085
|
-
pts.push({ x: worldX, y: worldY });
|
|
4086
|
-
}
|
|
4087
|
-
if (st.tool === "laser") {
|
|
4088
|
-
if (shouldAppendPoint) {
|
|
4089
|
-
setLaserTrail((prev) => [
|
|
4090
|
-
...prev,
|
|
4091
|
-
{ x: worldX, y: worldY, t: Date.now() }
|
|
4092
|
-
]);
|
|
4093
|
-
}
|
|
4094
|
-
return;
|
|
4095
|
-
}
|
|
4096
|
-
setPlacementPreview({
|
|
4097
|
-
kind: "stroke",
|
|
4098
|
-
tool: st.tool,
|
|
4099
|
-
points: [...pts],
|
|
4100
|
-
style: { ...strokeStyleRef.current }
|
|
4101
|
-
});
|
|
4102
|
-
return;
|
|
4103
|
-
}
|
|
4104
|
-
if (st.kind === "move") {
|
|
4105
|
-
const dx = worldX - st.startWorld.x;
|
|
4106
|
-
const dy = worldY - st.startWorld.y;
|
|
4107
|
-
const change = onItemsChangeRef.current;
|
|
4108
|
-
if (!change) return;
|
|
4109
|
-
const nextList = itemsRef.current.map((it) => {
|
|
4110
|
-
const snap = st.snapshots[it.id];
|
|
4111
|
-
if (!snap) return it;
|
|
4112
|
-
return {
|
|
4113
|
-
...snap,
|
|
4114
|
-
x: snap.x + dx,
|
|
4115
|
-
y: snap.y + dy,
|
|
4116
|
-
bounds: {
|
|
4117
|
-
...snap.bounds,
|
|
4118
|
-
x: snap.bounds.x + dx,
|
|
4119
|
-
y: snap.bounds.y + dy
|
|
4120
|
-
}
|
|
4121
|
-
};
|
|
4122
|
-
});
|
|
4123
|
-
change(nextList);
|
|
4124
|
-
return;
|
|
4125
|
-
}
|
|
4126
|
-
if (st.kind === "marquee") {
|
|
4127
|
-
const a = st.startWorld;
|
|
4128
|
-
const b = { x: worldX, y: worldY };
|
|
4129
|
-
const rect = {
|
|
4130
|
-
x: Math.min(a.x, b.x),
|
|
4131
|
-
y: Math.min(a.y, b.y),
|
|
4132
|
-
width: Math.abs(b.x - a.x),
|
|
4133
|
-
height: Math.abs(b.y - a.y)
|
|
4134
|
-
};
|
|
4135
|
-
setPlacementPreview({ kind: "marquee", rect });
|
|
4136
|
-
return;
|
|
4137
|
-
}
|
|
4138
|
-
if (st.kind === "erase") {
|
|
4139
|
-
setEraserTrail((prev) => [
|
|
4140
|
-
...prev,
|
|
4141
|
-
{ x: worldX, y: worldY, t: Date.now() }
|
|
4142
|
-
]);
|
|
4143
|
-
const toErase = collectEraserTargetsAtWorldPoint(
|
|
4144
|
-
itemsRef.current,
|
|
4145
|
-
worldX,
|
|
4146
|
-
worldY,
|
|
4147
|
-
{ lineHitWorld: 10 / cam.zoom, ignoreLocked: true }
|
|
4148
|
-
);
|
|
4149
|
-
for (const id of toErase) {
|
|
4150
|
-
eraserPreviewIdSetRef.current.add(id);
|
|
4151
|
-
}
|
|
4152
|
-
setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
|
|
4153
|
-
return;
|
|
4154
|
-
}
|
|
4155
|
-
if (st.kind === "place") {
|
|
4156
|
-
setPlacementPreview(
|
|
4157
|
-
placementPreviewForTool(st.tool, st.startWorld, {
|
|
4158
|
-
x: worldX,
|
|
4159
|
-
y: worldY
|
|
4160
|
-
})
|
|
4161
|
-
);
|
|
4162
|
-
return;
|
|
4163
|
-
}
|
|
4725
|
+
applyDragMoveAtScreenPoint({ x: sx, y: sy }, { x: pageX, y: pageY });
|
|
4164
4726
|
},
|
|
4165
4727
|
onPanResponderRelease: (evt) => {
|
|
4166
4728
|
lastPinchDist.current = null;
|
|
@@ -4204,6 +4766,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4204
4766
|
dragStateRef.current = { kind: "idle" };
|
|
4205
4767
|
return;
|
|
4206
4768
|
}
|
|
4769
|
+
if (st.kind === "resize" || st.kind === "rotate") {
|
|
4770
|
+
dragStateRef.current = { kind: "idle" };
|
|
4771
|
+
return;
|
|
4772
|
+
}
|
|
4207
4773
|
if (st.kind === "marquee") {
|
|
4208
4774
|
dragStateRef.current = { kind: "idle" };
|
|
4209
4775
|
setPlacementPreview(null);
|
|
@@ -4301,6 +4867,34 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4301
4867
|
requestSelectToolAfterUse();
|
|
4302
4868
|
return;
|
|
4303
4869
|
}
|
|
4870
|
+
if (st.kind === "custom-place") {
|
|
4871
|
+
dragStateRef.current = { kind: "idle" };
|
|
4872
|
+
setPlacementPreview(null);
|
|
4873
|
+
const change = onItemsChangeRef.current;
|
|
4874
|
+
if (!change) return;
|
|
4875
|
+
const { worldX, worldY } = screenToWorld(
|
|
4876
|
+
evt.nativeEvent.locationX,
|
|
4877
|
+
evt.nativeEvent.locationY
|
|
4878
|
+
);
|
|
4879
|
+
const center = {
|
|
4880
|
+
x: (st.startWorld.x + worldX) / 2,
|
|
4881
|
+
y: (st.startWorld.y + worldY) / 2
|
|
4882
|
+
};
|
|
4883
|
+
const raw = rectFromCorners(st.startWorld, { x: worldX, y: worldY });
|
|
4884
|
+
const normalized = normalizeRect(raw);
|
|
4885
|
+
const bounds = normalized.width < MIN_PLACE_SIZE || normalized.height < MIN_PLACE_SIZE ? {
|
|
4886
|
+
x: center.x - 60,
|
|
4887
|
+
y: center.y - 40,
|
|
4888
|
+
width: 120,
|
|
4889
|
+
height: 80
|
|
4890
|
+
} : normalized;
|
|
4891
|
+
const id = createShapeId();
|
|
4892
|
+
const item = st.placement.createItem({ id, bounds });
|
|
4893
|
+
change([...itemsRef.current, item]);
|
|
4894
|
+
onSelectionChangeRef.current?.([id]);
|
|
4895
|
+
requestSelectToolAfterUse();
|
|
4896
|
+
return;
|
|
4897
|
+
}
|
|
4304
4898
|
if (st.kind === "tap") {
|
|
4305
4899
|
dragStateRef.current = { kind: "idle" };
|
|
4306
4900
|
const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
|
|
@@ -4362,6 +4956,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4362
4956
|
}
|
|
4363
4957
|
}),
|
|
4364
4958
|
[
|
|
4959
|
+
applyDragMoveAtScreenPoint,
|
|
4365
4960
|
screenToWorld,
|
|
4366
4961
|
requestRender,
|
|
4367
4962
|
requestSelectToolAfterUse,
|
|
@@ -4392,7 +4987,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4392
4987
|
[requestRender, size]
|
|
4393
4988
|
);
|
|
4394
4989
|
const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
|
|
4395
|
-
const activeToolCursor =
|
|
4990
|
+
const activeToolCursor = cursorForToolId(toolId);
|
|
4396
4991
|
const toolCursor = activeToolCursor && toolCursorPoint ? { cursor: activeToolCursor, point: toolCursorPoint } : null;
|
|
4397
4992
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4398
4993
|
reactNative.View,
|