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