@vvfx/sdk 0.2.7 → 0.2.9

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.
Files changed (108) hide show
  1. package/dist/config.js +231 -0
  2. package/dist/exporter/config.js +18 -0
  3. package/dist/exporter/const.js +71 -0
  4. package/dist/exporter/export-media.js +878 -0
  5. package/dist/exporter/exporter.js +121 -0
  6. package/dist/exporter/index.js +1 -0
  7. package/dist/exporter/types.js +1 -0
  8. package/dist/exporter/utils.js +190 -0
  9. package/dist/exporter/wav-audio.js +50 -0
  10. package/dist/gesture-handler/gizmo/adsorption-gizmo.js +227 -0
  11. package/dist/gesture-handler/gizmo/control-gizmo.js +149 -0
  12. package/dist/gesture-handler/gizmo/gizmo.js +17 -0
  13. package/dist/gesture-handler/gizmo/icon-gizmo.js +189 -0
  14. package/dist/gesture-handler/gizmo/index.js +6 -0
  15. package/dist/gesture-handler/gizmo/item-create-gizmo.js +256 -0
  16. package/dist/gesture-handler/gizmo/loading-gizmo.js +320 -0
  17. package/dist/gesture-handler/gizmo/mask-gizmo.js +648 -0
  18. package/dist/gesture-handler/gizmo/picture-cut-gizmo.js +550 -0
  19. package/dist/gesture-handler/gizmo/picture-expand-gizmo.js +491 -0
  20. package/dist/gesture-handler/gizmo/preference-gizmo.js +148 -0
  21. package/dist/gesture-handler/gizmo/selector-gizmo.d.ts +1 -1
  22. package/dist/gesture-handler/gizmo/selector-gizmo.js +468 -0
  23. package/dist/gesture-handler/gizmo/sprite-text-edit-gizmo.js +247 -0
  24. package/dist/gesture-handler/gizmo/text-gizmo.js +1033 -0
  25. package/dist/gesture-handler/gizmo/transform-gizmo.js +1199 -0
  26. package/dist/gesture-handler/gizmo/type.js +18 -0
  27. package/dist/gesture-handler/index.js +3 -0
  28. package/dist/gesture-handler/src/gesture-handler.js +770 -0
  29. package/dist/gesture-handler/src/icons.js +24 -0
  30. package/dist/gesture-handler/utils.js +40 -0
  31. package/dist/html-overlay/anchor-navigation-runtime.js +27 -0
  32. package/dist/html-overlay/auto-height-runtime.js +132 -0
  33. package/dist/html-overlay/document-runtime.js +71 -0
  34. package/dist/html-overlay/document-transform.js +96 -0
  35. package/dist/html-overlay/dom-order.js +8 -0
  36. package/dist/html-overlay/host-message-runtime.js +24 -0
  37. package/dist/html-overlay/html-capture-runtime.js +282 -0
  38. package/dist/html-overlay/html-viewport-style.js +10 -0
  39. package/dist/html-overlay/index.js +1 -0
  40. package/dist/html-overlay/manager.js +1663 -0
  41. package/dist/html-overlay/mime-utils.js +21 -0
  42. package/dist/html-overlay/overlay-transform.js +57 -0
  43. package/dist/html-overlay/path-utils.js +35 -0
  44. package/dist/html-overlay/shell-runtime.js +12 -0
  45. package/dist/index.cjs +1 -7
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.js +13 -75317
  48. package/dist/index.mjs +75311 -0
  49. package/dist/index.mjs.map +1 -0
  50. package/dist/layer-order.js +8 -0
  51. package/dist/math/box2.js +396 -0
  52. package/dist/math/circle.js +131 -0
  53. package/dist/math/euler.js +35 -0
  54. package/dist/math/index.js +13 -0
  55. package/dist/math/line2.js +153 -0
  56. package/dist/math/line3.js +36 -0
  57. package/dist/math/matrix4.js +207 -0
  58. package/dist/math/plane.js +52 -0
  59. package/dist/math/quaternion.js +3 -0
  60. package/dist/math/ray-caster.js +25 -0
  61. package/dist/math/ray.js +47 -0
  62. package/dist/math/type.js +1 -0
  63. package/dist/math/utils.js +77 -0
  64. package/dist/math/vector2.js +70 -0
  65. package/dist/math/vector3.js +60 -0
  66. package/dist/screen-shot/index.js +1 -0
  67. package/dist/screen-shot/screen-shot.js +113 -0
  68. package/dist/sdk-item/base-item.js +160 -0
  69. package/dist/sdk-item/card-item.js +131 -0
  70. package/dist/sdk-item/effects-item.js +144 -0
  71. package/dist/sdk-item/frame-item.js +217 -0
  72. package/dist/sdk-item/generator-item.js +188 -0
  73. package/dist/sdk-item/group-item.js +116 -0
  74. package/dist/sdk-item/index.js +39 -0
  75. package/dist/sdk-item/sprite-item.js +147 -0
  76. package/dist/sdk-item/text-item.js +223 -0
  77. package/dist/sdk-item/types.d.ts +1 -11
  78. package/dist/sdk-item/types.js +15 -0
  79. package/dist/sdk-item/video-item.js +174 -0
  80. package/dist/sdk.js +1903 -0
  81. package/dist/service/UndoRedo.js +79 -0
  82. package/dist/shared/index.d.ts +1 -0
  83. package/dist/shared/index.js +2 -0
  84. package/dist/shared/player.d.ts +1 -1
  85. package/dist/shared/player.js +9 -0
  86. package/dist/shared/spec.d.ts +1 -0
  87. package/dist/shared/spec.js +1 -0
  88. package/dist/size-adapte/index.js +1 -0
  89. package/dist/size-adapte/size-adapt.js +395 -0
  90. package/dist/types.d.ts +5 -1
  91. package/dist/types.js +17 -0
  92. package/dist/utils/background-manager.js +189 -0
  93. package/dist/utils/common-utils.js +183 -0
  94. package/dist/utils/index.js +8 -0
  95. package/dist/utils/interaction-utils.js +205 -0
  96. package/dist/utils/json-data-utils.js +1529 -0
  97. package/dist/utils/layout-utils.js +1217 -0
  98. package/dist/utils/page-data-utils.js +5397 -0
  99. package/dist/utils/player-data-utils.js +48 -0
  100. package/dist/utils/types.js +8 -0
  101. package/dist/wireframe/common/box.js +18 -0
  102. package/dist/wireframe/common/dashed-line.js +21 -0
  103. package/dist/wireframe/common/line.js +17 -0
  104. package/dist/wireframe/common/pixi-ext.js +1 -0
  105. package/dist/wireframe/common/pixi.js +31 -0
  106. package/dist/wireframe/index.js +4 -0
  107. package/package.json +10 -3
  108. package/dist/index.js.map +0 -1
@@ -0,0 +1,1033 @@
1
+ import { Graphics, Sprite } from '../../wireframe';
2
+ import { SDK } from '../../sdk';
3
+ import { Gizmo } from './gizmo';
4
+ import { assertExist, spec } from '../../shared';
5
+ import { Box2, getAngleByVectors, getVector2Angle, Line2, RAD2DEG, roundNumber, Vector2, Vector3, DEG2RAD, getAbsMaxIndex, isEqual, Euler, Quaternion, Matrix4 } from '../../math';
6
+ import { FrameLayoutMode } from '../../types';
7
+ import { SDKItemType } from '../../sdk-item/types';
8
+ import { isEffectsItem, isFrameItem, isGroupItem } from '../../sdk-item';
9
+ import { calculateAutoLayoutIndicatorLine, calculateAutoLayoutPositionByMouse, calculateInsertPositionFromLayout, adjustOtherElementsLayoutInfo, updateItemLayoutInfo, } from '../../utils/layout-utils';
10
+ const textareaDefaultCssClass = `
11
+ word-break: break-all;
12
+ position: absolute;
13
+ border: none;
14
+ margin: 0;
15
+ padding: 0;
16
+ outline: none !important;
17
+ overflow: hidden;
18
+ resize: none;
19
+ display: none;
20
+ background: transparent;
21
+ opacity: .8;
22
+ color: transparent;
23
+ caret-color: #000000;
24
+ field-sizing: content;
25
+ -webkit-text-fill-color: transparent;
26
+ text-fill-color: transparent;
27
+ `;
28
+ function applyCSSClass(element, className, cssText) {
29
+ // 处理嵌套写法,将 & 替换为类名
30
+ const processedCSS = cssText.replace(/&/g, `.${className}`);
31
+ // 创建样式元素
32
+ const style = document.createElement('style');
33
+ style.textContent = `
34
+ .${className} {
35
+ ${processedCSS}
36
+ }
37
+ `;
38
+ // 添加到文档头
39
+ document.head.appendChild(style);
40
+ // 给元素添加类名
41
+ element.classList.add(className);
42
+ // 返回清理函数(可选)
43
+ return () => {
44
+ element.classList.remove(className);
45
+ document.head.removeChild(style);
46
+ };
47
+ }
48
+ export class TextGizmo extends Gizmo {
49
+ type = 'text';
50
+ _eventEmitter;
51
+ /**
52
+ * @description 用于绘制选中态
53
+ */
54
+ textAreaElement;
55
+ /**
56
+ * @description 用于绘制选中包围盒
57
+ */
58
+ graphics = new Graphics();
59
+ /**
60
+ * @description 数据刷新工具
61
+ */
62
+ _pageDataUtils;
63
+ /**
64
+ * @description 绘制结果
65
+ */
66
+ result;
67
+ /**
68
+ * @description 交互参数
69
+ */
70
+ interactionParam;
71
+ /**
72
+ * @description 吸附交互器
73
+ */
74
+ _adsorptionGizmo;
75
+ /**
76
+ * @description 鼠标交互样式结果
77
+ */
78
+ cursorResult = {
79
+ type: 'normal',
80
+ angle: 0,
81
+ };
82
+ /**
83
+ * @description 退出编辑态时忽视交互
84
+ */
85
+ ignoreInteraction = false;
86
+ /**
87
+ * @description 缩放方向
88
+ */
89
+ scaleParam;
90
+ rotationSprite = new Sprite();
91
+ cursorPoint = new Vector2();
92
+ _selectorGizmo;
93
+ /**
94
+ * @description 画板包围盒信息
95
+ */
96
+ frameBoxes = new Map();
97
+ /**
98
+ * @description
99
+ */
100
+ toAddedFrameId;
101
+ /**
102
+ * @description 需要处理子元素分离的组信息
103
+ * key: groupId, value: 需要分离的子元素信息
104
+ */
105
+ groupsNeedProcess = new Map();
106
+ /**
107
+ * @description 自动布局插入指示器
108
+ */
109
+ autoLayoutIndicator = null;
110
+ /**
111
+ * @description 是否在自动布局拖拽中
112
+ */
113
+ isAutoLayoutDragging = false;
114
+ /**
115
+ * @description 被拖拽元素的原始行列位置(用于自动布局时调整其他元素)
116
+ */
117
+ autoLayoutDragOriginalPosition = null;
118
+ /**
119
+ * @description 待应用的布局位置(用于延迟执行行列修改)
120
+ */
121
+ pendingLayoutPosition = null;
122
+ /**
123
+ * @description 自动布局指示器 Graphics
124
+ */
125
+ autoLayoutGraphics = new Graphics();
126
+ get interactive() {
127
+ return SDK.config.gestureHandlerConfig.textGizmoEnbaled;
128
+ }
129
+ set interactive(bool) {
130
+ SDK.config.gestureHandlerConfig.textGizmoEnbaled = bool;
131
+ }
132
+ constructor(pageDataUtils, adsorptionGizmo, selectorGizmo, eventEmitter, parent) {
133
+ super();
134
+ this._pageDataUtils = pageDataUtils;
135
+ this._adsorptionGizmo = adsorptionGizmo;
136
+ this._selectorGizmo = selectorGizmo;
137
+ this._eventEmitter = eventEmitter;
138
+ this.textAreaElement = document.createElement('textarea');
139
+ this.textAreaElement.style.display = 'none';
140
+ parent?.appendChild(this.textAreaElement);
141
+ applyCSSClass(this.textAreaElement, 'text-gizmo-custom-style', textareaDefaultCssClass);
142
+ this.result = {
143
+ type: 'empty'
144
+ };
145
+ this.interactionParam = {
146
+ active: false,
147
+ type: 'none',
148
+ anchor: new Vector2(),
149
+ lastPoint: new Vector2(),
150
+ startPoint: new Vector2(),
151
+ lastWorldPosition: new Vector3(),
152
+ };
153
+ }
154
+ preparationAction(event) {
155
+ if (!this.interactive) {
156
+ console.warn('Text gizmo is not opened, use #SDK.openTextGizmo() open this feature.');
157
+ return undefined;
158
+ }
159
+ if (this.result.type === 'empty') {
160
+ return undefined;
161
+ }
162
+ const mouse = new Vector2(event.offsetX, event.offsetY);
163
+ const clineMouse = new Vector2(event.clientX, event.clientY);
164
+ this.refreshInteractionParam(mouse, clineMouse);
165
+ const viewportPoint = mouse.subtract(this._pageDataUtils.getViewportLeftTopPosition());
166
+ this.initInteractionPlane(viewportPoint);
167
+ this.cursorPoint.set(event.offsetX, event.offsetY);
168
+ this.refreshCursorResult();
169
+ if (this.interactionParam.type === 'none') {
170
+ return undefined;
171
+ }
172
+ else {
173
+ const type = this.interactionParam.type === 'translation' ? this._selectorGizmo.preparationAction(event, 'text') : this.type;
174
+ this.interactionParam.active = type === 'text';
175
+ return this.interactionParam.active ? type : undefined;
176
+ }
177
+ }
178
+ preAction(event, isDoubleClick) {
179
+ if (event.buttons !== 1) {
180
+ return this.type;
181
+ }
182
+ const { offsetX, offsetY } = event;
183
+ const mouse = new Vector2(offsetX, offsetY);
184
+ this.refreshFrameBoxes();
185
+ if (isDoubleClick) {
186
+ if (this.result.type !== 'empty' && !this.result.box.containsPoint(mouse)) {
187
+ if (this.interactionParam.type === 'none') {
188
+ this.result = { type: 'empty' };
189
+ this._pageDataUtils.clearSelectedItems(true);
190
+ return undefined;
191
+ }
192
+ }
193
+ else {
194
+ this._pageDataUtils.saveUndoRedoOldData();
195
+ this.interactionParam.type = 'edit';
196
+ this.interactionParam.textareaType = 'focus';
197
+ }
198
+ }
199
+ else {
200
+ if (this.result.type !== 'empty' && !this.result.box.containsPoint(mouse)) {
201
+ if (this.interactionParam.type === 'edit') {
202
+ this.interactionParam.type = 'none';
203
+ this.ignoreInteraction = true;
204
+ }
205
+ else if (this.interactionParam.type === 'none') {
206
+ this.result = { type: 'empty' };
207
+ return undefined;
208
+ }
209
+ }
210
+ else {
211
+ this._pageDataUtils.saveUndoRedoOldData();
212
+ const viewportPoint = mouse.subtract(this._pageDataUtils.getViewportLeftTopPosition());
213
+ this.initInteractionPlane(viewportPoint);
214
+ this.interactionParam.lastWorldPosition = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(viewportPoint) ?? new Vector3();
215
+ this.refreshCursorResult();
216
+ }
217
+ }
218
+ return this.type;
219
+ }
220
+ action(event) {
221
+ if (!this.interactive) {
222
+ console.warn('Text gizmo is not opened, use #SDK.setTextConfig() open this feature.');
223
+ return;
224
+ }
225
+ // 更新鼠标位置(用于自动布局计算)
226
+ this.cursorPoint.set(event.offsetX, event.offsetY);
227
+ if (this.interactionParam.type === 'edit' || this.result.type === 'empty' || this.ignoreInteraction) {
228
+ return;
229
+ }
230
+ if (!this.interactionParam.active) {
231
+ this.interactionParam.active = true;
232
+ this.interactionParam.type = 'translation';
233
+ this.interactionParam.lastPoint = new Vector2(event.clientX, event.clientY);
234
+ this.interactionParam.startPoint = new Vector2(event.offsetX, event.offsetY);
235
+ const viewportPoint = new Vector2(event.offsetX, event.offsetY).subtract(this._pageDataUtils.getViewportLeftTopPosition());
236
+ this.initInteractionPlane(viewportPoint);
237
+ this.interactionParam.lastWorldPosition = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(viewportPoint) ?? new Vector3();
238
+ this.refreshFrameBoxes();
239
+ }
240
+ const { clientX, clientY, offsetX, offsetY } = event;
241
+ const currentClientPoint = new Vector2(clientX, clientY);
242
+ const { type, lastPoint, startPoint, anchor } = this.interactionParam;
243
+ const shift = new Vector2().subtractVectors(currentClientPoint, lastPoint);
244
+ const selectedItems = this._pageDataUtils.getSelectedItems();
245
+ switch (type) {
246
+ // 旋转交互
247
+ case 'rotation': {
248
+ const rotation = new Vector3();
249
+ const vecStart = new Vector2().subtractVectors(startPoint, anchor);
250
+ const vecCurrent = new Vector2().subtractVectors(startPoint.add(shift), anchor);
251
+ const angle = roundNumber(getAngleByVectors(vecStart, vecCurrent) * RAD2DEG, 3);
252
+ rotation.z = angle;
253
+ selectedItems.forEach(item => {
254
+ this._pageDataUtils.rotateItem(item.id, rotation);
255
+ });
256
+ this.interactionParam.lastPoint.copyFrom(currentClientPoint);
257
+ break;
258
+ }
259
+ case 'translation': {
260
+ // 平移
261
+ const move = shift.clone();
262
+ if (this._adsorptionGizmo.interactive) {
263
+ const pageData = this._pageDataUtils.getPageData();
264
+ assertExist(pageData);
265
+ const targetBoxes = pageData.items.map(item => {
266
+ if (!selectedItems.find(si => si.id === item.id)
267
+ && selectedItems.filter(selectedItem => item.parentId && item.parentId === selectedItem.id).length === 0
268
+ && this._pageDataUtils.getItemEditEnable(item.id)
269
+ && !selectedItems.find(si => si.parentId === item.id)) {
270
+ return this._pageDataUtils.getViewBoxById(item.id);
271
+ }
272
+ else {
273
+ return new Box2();
274
+ }
275
+ }).filter(box => !box.isEmpty());
276
+ const preAbsorptionDistance = new Vector2(this._adsorptionGizmo.absorptionResult.x ?? 0, this._adsorptionGizmo.absorptionResult.y ?? 0);
277
+ this._adsorptionGizmo.boxTranslationAdsorption(move, this.result.box, targetBoxes);
278
+ const absorptionDistance = new Vector2(this._adsorptionGizmo.absorptionResult.x ?? 0, this._adsorptionGizmo.absorptionResult.y ?? 0);
279
+ move.add(preAbsorptionDistance).subtract(absorptionDistance);
280
+ }
281
+ const worldPosition = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(startPoint.add(move).clone().subtract(this._pageDataUtils.getViewportLeftTopPosition()));
282
+ if (worldPosition) {
283
+ const translation = worldPosition.clone().subtract(this.interactionParam.lastWorldPosition);
284
+ selectedItems.forEach(item => {
285
+ translation.x = roundNumber(translation.x, 5);
286
+ translation.y = roundNumber(translation.y, 5);
287
+ translation.z = roundNumber(translation.z, 5);
288
+ this._pageDataUtils.moveItem(item.id, translation);
289
+ this.refreshItemFrameRelationShip(item, new Vector2(offsetX, offsetY));
290
+ });
291
+ this.interactionParam.lastWorldPosition.copyFrom(worldPosition);
292
+ this.interactionParam.lastPoint.copyFrom(currentClientPoint);
293
+ // 检查是否是自动布局画板的子元素
294
+ selectedItems.forEach(item => {
295
+ this.updateAutoLayoutIndicator(item);
296
+ });
297
+ }
298
+ break;
299
+ }
300
+ case 'scale': {
301
+ if (shift.length()) {
302
+ // 缩放
303
+ const resultViewPoint = this.cursorPoint.add(shift).clone().subtract(this._pageDataUtils.getViewportLeftTopPosition());
304
+ if (!startPoint || !this.scaleParam || !shift.length()) {
305
+ return;
306
+ }
307
+ const farthestCorner = this.scaleParam.farthestCorner;
308
+ const isValidScale = (startPoint.x - farthestCorner.x) * (this.cursorPoint.x - farthestCorner.x) > 0 && (startPoint.y - farthestCorner.y) * (this.cursorPoint.y - farthestCorner.y) > 0;
309
+ if (!isValidScale) {
310
+ resultViewPoint.x = farthestCorner.x + (startPoint.x - farthestCorner.x) / Math.abs(startPoint.x - farthestCorner.x) * 4;
311
+ resultViewPoint.y = farthestCorner.y + (startPoint.y - farthestCorner.y) / Math.abs(startPoint.y - farthestCorner.y) * 4;
312
+ }
313
+ const worldPosition = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(resultViewPoint);
314
+ if (worldPosition) {
315
+ const selectedItem = this._pageDataUtils.getSelectedItems()[0] ?? undefined;
316
+ const itemTransform = this._pageDataUtils.getItemTransformById(selectedItem?.id ?? '');
317
+ const rotationMatrix = new Matrix4().extractRotation(itemTransform.matrix).invert();
318
+ const { corner, center, shift: cornerShift, lastScalar } = this.scaleParam;
319
+ const startScale = new Vector3().subtractVectors(corner, center).applyMatrix(rotationMatrix);
320
+ const currentStartScale = startScale.clone().multiply(lastScalar);
321
+ const currentScale = worldPosition.clone().subtract(cornerShift).subtract(center).applyMatrix(rotationMatrix);
322
+ const scalar = new Vector3(1, 1, 1);
323
+ const targetIndex = getAbsMaxIndex([
324
+ isEqual(startScale.x, 0) ? 1 : Math.abs(currentScale.x / startScale.x),
325
+ isEqual(startScale.y, 0) ? 1 : Math.abs(currentScale.y / startScale.y),
326
+ ]);
327
+ const currentLockScalar = currentScale.toArray()[targetIndex];
328
+ const targetScalar = currentStartScale.toArray()[targetIndex];
329
+ if (isEqual(lastScalar.x, lastScalar.y) && isEqual(lastScalar.z, lastScalar.x)) {
330
+ const resultScalar = isEqual(targetScalar, 0) ? 1 : Math.abs(currentLockScalar / targetScalar);
331
+ scalar.set(resultScalar, resultScalar, resultScalar);
332
+ }
333
+ else {
334
+ if (targetIndex === 0) {
335
+ scalar.x = isEqual(targetScalar, 0) ? 1 : Math.abs(currentLockScalar / targetScalar);
336
+ const resultScalarY = startScale.y * currentLockScalar / startScale.x;
337
+ scalar.y = isEqual(currentStartScale.y, 0) ? 1 : resultScalarY / currentStartScale.y;
338
+ const resultScalarZ = startScale.z * currentLockScalar / startScale.x;
339
+ scalar.z = isEqual(currentStartScale.z, 0) ? 1 : resultScalarZ / currentStartScale.z;
340
+ }
341
+ else if (targetIndex === 1) {
342
+ scalar.y = isEqual(targetScalar, 0) ? 1 : Math.abs(currentLockScalar / targetScalar);
343
+ const resultScalarX = startScale.x * currentLockScalar / startScale.y;
344
+ scalar.x = isEqual(currentStartScale.x, 0) ? 1 : resultScalarX / currentStartScale.x;
345
+ const resultScalarZ = startScale.z * currentLockScalar / startScale.y;
346
+ scalar.z = isEqual(currentStartScale.z, 0) ? 1 : resultScalarZ / currentStartScale.z;
347
+ }
348
+ else {
349
+ scalar.z = isEqual(targetScalar, 0) ? 1 : Math.abs(currentLockScalar / targetScalar);
350
+ const resultScalarX = startScale.x * currentLockScalar / startScale.z;
351
+ scalar.x = isEqual(currentStartScale.x, 0) ? 1 : resultScalarX / currentStartScale.x;
352
+ const resultScalarY = startScale.y * currentLockScalar / startScale.z;
353
+ scalar.y = isEqual(currentStartScale.y, 0) ? 1 : resultScalarY / currentStartScale.y;
354
+ }
355
+ }
356
+ const resultScalar = scalar.clone().subtract(new Vector3(1, 1, 0)).divide(2).add(new Vector3(1, 1, 0.5));
357
+ const originCorner = center.clone().add(new Vector3().subtractVectors(corner, center).multiply(new Vector3(lastScalar.x, lastScalar.y, 1)));
358
+ this.scaleParam.lastScalar.multiply(resultScalar);
359
+ const currentCorner = center.clone().add(new Vector3().subtractVectors(corner, center).multiply(new Vector3(this.scaleParam.lastScalar.x, this.scaleParam.lastScalar.y, 1)));
360
+ const translation = new Vector3().subtractVectors(currentCorner, originCorner);
361
+ translation.x = roundNumber(translation.x, 5);
362
+ translation.y = roundNumber(translation.y, 5);
363
+ translation.z = roundNumber(translation.z, 5);
364
+ this.scaleParam.center.add(translation);
365
+ this.scaleParam.corner.add(translation);
366
+ selectedItems.forEach(item => {
367
+ this._pageDataUtils.scaleTextItem(item.id, resultScalar.x);
368
+ this._pageDataUtils.moveItem(item.id, translation);
369
+ });
370
+ this.interactionParam.lastWorldPosition.copyFrom(worldPosition);
371
+ }
372
+ }
373
+ break;
374
+ }
375
+ case 'width-scale': {
376
+ const lastPoint = startPoint.clone();
377
+ const currentPoint = startPoint.clone().add(shift);
378
+ if (shift.length()) {
379
+ const interactionLine = new Vector2().subtractVectors(lastPoint, anchor);
380
+ const currentline = new Vector2().subtractVectors(currentPoint, anchor);
381
+ const dotLength = Math.max(currentline.dot(interactionLine) / interactionLine.length(), 1);
382
+ const originLength = interactionLine.length();
383
+ const scalar = (dotLength / originLength - 1) / 2 + 1;
384
+ const resultPoint = anchor.clone().add(interactionLine.clone().normalize().scale(originLength * scalar));
385
+ const moveShift = new Vector2().subtractVectors(resultPoint, lastPoint);
386
+ startPoint.add(moveShift);
387
+ const worldTranslation = this._pageDataUtils.interactionUtils.getWorldSizeByViewSize(moveShift);
388
+ worldTranslation.x = roundNumber(worldTranslation.x, 5);
389
+ worldTranslation.y = roundNumber(worldTranslation.y, 5);
390
+ worldTranslation.z = roundNumber(worldTranslation.z, 5);
391
+ selectedItems.forEach(item => {
392
+ this._pageDataUtils.moveItem(item.id, worldTranslation);
393
+ });
394
+ selectedItems.forEach(item => {
395
+ this._pageDataUtils.scaleTextItemWidth(item.id, scalar);
396
+ });
397
+ }
398
+ break;
399
+ }
400
+ }
401
+ this.interactionParam.lastPoint.copyFrom(currentClientPoint);
402
+ this.refreshCursorResult();
403
+ }
404
+ endAction(event) {
405
+ this.ignoreInteraction = false;
406
+ const mouse = new Vector2(event.offsetX, event.offsetY);
407
+ const clineMouse = new Vector2(event.clientX, event.clientY);
408
+ // 执行自动布局重排序(如果在自动布局拖拽中)
409
+ const selectedItems = this._pageDataUtils.getSelectedItems();
410
+ const draggedItem = selectedItems.length === 1 ? selectedItems[0] : undefined;
411
+ this.executeAutoLayoutReorder(draggedItem);
412
+ // 存入 undo redo 栈
413
+ this._pageDataUtils.pushUndoRedoData();
414
+ this.toAddedFrameId = undefined;
415
+ this._adsorptionGizmo.clearAbsorptionResult();
416
+ this.refreshInteractionParam(mouse, clineMouse);
417
+ this.refreshCursorResult();
418
+ return this.type;
419
+ }
420
+ interruption() {
421
+ this.interactionParam.type = 'none';
422
+ this._adsorptionGizmo.clearAbsorptionResult();
423
+ // 清空自动布局相关状态(交互被打断时不执行行列修改)
424
+ if (this.isAutoLayoutDragging) {
425
+ this.autoLayoutIndicator = null;
426
+ this.isAutoLayoutDragging = false;
427
+ this.autoLayoutDragOriginalPosition = null;
428
+ this.pendingLayoutPosition = null;
429
+ }
430
+ return this.type;
431
+ }
432
+ actionKey(keyCode) { }
433
+ endActionKey(keyCode) { }
434
+ cancelPreparation() { }
435
+ refreshResults() {
436
+ if (!this.interactive) {
437
+ return;
438
+ }
439
+ const selectedIds = this._pageDataUtils.getPageData()?.activeData?.selectedItems;
440
+ const viewItem = this._pageDataUtils.getSDKItem(selectedIds?.[0] ?? '');
441
+ if (selectedIds?.length !== 1 || viewItem?.type !== SDKItemType.TEXT) {
442
+ this.result = {
443
+ type: 'empty'
444
+ };
445
+ return;
446
+ }
447
+ const viewBox = this._pageDataUtils.getViewBoxById(selectedIds[0]);
448
+ if (!viewBox.isEmpty()) {
449
+ const widthScaleCorners = [new Line2(viewBox.corners[0], viewBox.corners[1]).at(0.5), new Line2(viewBox.corners[2], viewBox.corners[3]).at(0.5)];
450
+ const rotationLine = new Line2(viewBox.getCenter(), new Line2(viewBox.corners[0], viewBox.corners[3]).at(0.5));
451
+ const rotationCorner = rotationLine.at((rotationLine.length() + 22) / rotationLine.length());
452
+ this.result = {
453
+ type: 'valid',
454
+ box: viewBox.clone(),
455
+ widthScaleCorners,
456
+ rotationCorner,
457
+ rotation: -viewItem.property.rotation[2] * DEG2RAD,
458
+ };
459
+ }
460
+ else {
461
+ this.result = {
462
+ type: 'empty'
463
+ };
464
+ }
465
+ }
466
+ refreshInteractionParam(mouse, mouseClient) {
467
+ if (this.result.type === 'empty') {
468
+ this.interactionParam.active = false;
469
+ this.interactionParam.type = 'none';
470
+ this.interactionParam.lastPoint = new Vector2();
471
+ this.interactionParam.startPoint = new Vector2();
472
+ this.interactionParam.lastWorldPosition = new Vector3();
473
+ return;
474
+ }
475
+ if (this.interactionParam.type === 'edit') {
476
+ return;
477
+ }
478
+ const { rotateInteractMode, scaleInteractionDistance, rotationCornerInteractionDistance, rotationTopCenterInteractionDistance, } = SDK.config.gestureHandlerConfig.textGizmoConfig;
479
+ const anchor = this.result.box.getCenter();
480
+ for (const widthScaleCorner of this.result.widthScaleCorners) {
481
+ const box = new Box2().setFromCenterAndSize(widthScaleCorner, new Vector2(6, 18));
482
+ if (box.containsPoint(mouse)) {
483
+ this.interactionParam.active = true;
484
+ this.interactionParam.type = 'width-scale';
485
+ this.interactionParam.anchor = anchor;
486
+ this.interactionParam.lastPoint = mouse.clone();
487
+ this.interactionParam.startPoint = widthScaleCorner.clone();
488
+ this.interactionParam.lastWorldPosition = new Vector3();
489
+ return;
490
+ }
491
+ }
492
+ const scaleCornerIndex = this.result.box.corners.findIndex(c => c.distanceTo(mouse) < scaleInteractionDistance);
493
+ if (scaleCornerIndex >= 0) {
494
+ const scaleCorner = this.result.box.corners[scaleCornerIndex];
495
+ const scaleAnchor = this.result.box.corners[(scaleCornerIndex + 2) % 4];
496
+ this.interactionParam.active = true;
497
+ this.interactionParam.type = 'scale';
498
+ this.interactionParam.anchor = scaleAnchor.clone();
499
+ this.interactionParam.lastPoint = mouseClient.clone();
500
+ this.interactionParam.startPoint = scaleCorner.clone();
501
+ return;
502
+ }
503
+ if (this.result.box.containsPoint(mouse, false)) {
504
+ this.interactionParam.active = true;
505
+ this.interactionParam.type = 'translation';
506
+ this.interactionParam.lastPoint = mouseClient.clone();
507
+ this.interactionParam.startPoint = mouse.clone();
508
+ this.interactionParam.lastWorldPosition = new Vector3();
509
+ return;
510
+ }
511
+ switch (rotateInteractMode) {
512
+ case 'top-center': {
513
+ if (mouse.distanceTo(this.result.rotationCorner) < rotationTopCenterInteractionDistance) {
514
+ this.interactionParam.active = true;
515
+ this.interactionParam.type = 'rotation';
516
+ this.interactionParam.anchor = anchor;
517
+ this.interactionParam.lastPoint = mouseClient.clone();
518
+ this.interactionParam.startPoint = this.result.rotationCorner.clone();
519
+ this.interactionParam.lastWorldPosition = new Vector3();
520
+ return;
521
+ }
522
+ break;
523
+ }
524
+ case 'corner': {
525
+ const rotationCorner = this.result.box.corners.find(c => c.distanceTo(mouse) < rotationCornerInteractionDistance);
526
+ if (rotationCorner) {
527
+ this.interactionParam.active = true;
528
+ this.interactionParam.type = 'rotation';
529
+ this.interactionParam.anchor = anchor;
530
+ this.interactionParam.lastPoint = mouseClient.clone();
531
+ this.interactionParam.startPoint = rotationCorner.clone();
532
+ this.interactionParam.lastWorldPosition = new Vector3();
533
+ return;
534
+ }
535
+ break;
536
+ }
537
+ default: {
538
+ break;
539
+ }
540
+ }
541
+ this.interactionParam.active = false;
542
+ this.interactionParam.type = 'none';
543
+ this.interactionParam.lastPoint = new Vector2();
544
+ this.interactionParam.startPoint = new Vector2();
545
+ this.interactionParam.lastWorldPosition = new Vector3();
546
+ return;
547
+ }
548
+ refreshCursorResult() {
549
+ const angle = (getVector2Angle(new Vector2().subtractVectors(this.interactionParam.startPoint, this.interactionParam.anchor)) + Math.PI / 4) * RAD2DEG;
550
+ switch (this.interactionParam.type) {
551
+ case 'edit':
552
+ case 'none':
553
+ case 'translation': {
554
+ this.cursorResult = {
555
+ type: 'normal',
556
+ angle: 0
557
+ };
558
+ break;
559
+ }
560
+ case 'scale':
561
+ case 'width-scale': {
562
+ this.cursorResult = {
563
+ type: 'scale',
564
+ angle,
565
+ };
566
+ break;
567
+ }
568
+ case 'rotation': {
569
+ this.cursorResult = {
570
+ type: 'rotation',
571
+ angle,
572
+ };
573
+ break;
574
+ }
575
+ }
576
+ }
577
+ getRenderObjects() {
578
+ const result = [];
579
+ if (!this.interactive || this.result.type === 'empty') {
580
+ return [];
581
+ }
582
+ this.graphics.clear();
583
+ const { box, widthScaleCorners, rotationCorner, rotation, } = this.result;
584
+ const { boxLineWidth, boxLineColor, wireframeAlpha, wireframeColor, wireframeWidth, rotateInteractMode, rotationCornerWidth, scaleCornerStrokeWidth, scaleCornerStrokeColor, rotationCornerRadius, rotationCornerFillColor, rotationCornerStrokeWidth, rotationCornerStrokeColor, } = SDK.config.gestureHandlerConfig.textGizmoConfig;
585
+ this.graphics.lineStyle(boxLineWidth, boxLineColor);
586
+ this.graphics.drawBox(box);
587
+ if (this.interactionParam.type !== 'edit') {
588
+ if (box.getSize().y > 25) {
589
+ widthScaleCorners.forEach(corner => {
590
+ const widthScaleBox = new Box2().setFromCenterAndSize(corner, new Vector2(8, 15));
591
+ this.graphics.beginFill(0xFFFFFF);
592
+ this.graphics.fillBox(widthScaleBox);
593
+ this.graphics.endFill();
594
+ this.graphics.lineStyle(scaleCornerStrokeWidth, scaleCornerStrokeColor);
595
+ this.graphics.drawBox(widthScaleBox);
596
+ });
597
+ }
598
+ switch (rotateInteractMode) {
599
+ case 'top-center': {
600
+ this.rotationSprite.width = rotationCornerWidth;
601
+ this.rotationSprite.height = rotationCornerWidth;
602
+ this.rotationSprite.anchor.set(0.5, 0.5);
603
+ this.rotationSprite.x = rotationCorner.x;
604
+ this.rotationSprite.y = rotationCorner.y;
605
+ this.rotationSprite.rotation = rotation;
606
+ result.push(this.rotationSprite);
607
+ break;
608
+ }
609
+ case 'corner': {
610
+ box.corners.forEach(corner => {
611
+ this.graphics.lineStyle(rotationCornerStrokeWidth, rotationCornerStrokeColor);
612
+ this.graphics.beginFill(rotationCornerFillColor);
613
+ this.graphics.drawCircle(corner.x, corner.y, rotationCornerRadius);
614
+ this.graphics.endFill();
615
+ });
616
+ break;
617
+ }
618
+ }
619
+ if (this.toAddedFrameId) {
620
+ const frameBox = this.frameBoxes.get(this.toAddedFrameId);
621
+ if (frameBox) {
622
+ this.graphics.lineStyle(wireframeWidth, wireframeColor, wireframeAlpha);
623
+ this.graphics.drawBox(frameBox);
624
+ }
625
+ }
626
+ }
627
+ else if (this.interactionParam.type === 'edit' && this.textAreaElement.style.display === 'none') {
628
+ const selectedItems = this._pageDataUtils.getSelectedItems();
629
+ const viewportParam = this._pageDataUtils.interactionUtils.viewportParam;
630
+ const selectedItem = selectedItems[0];
631
+ const property = selectedItem?.property;
632
+ const originBox = box.clone().rotate(property.rotation[2] * DEG2RAD);
633
+ const leftTop = originBox.min.clone();
634
+ const size = originBox.getSize();
635
+ this.textAreaElement.value = property.text ?? '';
636
+ this.textAreaElement.style.textAlign = this.getTextAlign(property.textAlign);
637
+ this.textAreaElement.style.fontFamily = property.fontFamily;
638
+ this.textAreaElement.style.fontSize = `${property.fontSize * viewportParam.scale}px`;
639
+ this.textAreaElement.style.fontWeight = property.fontWeight;
640
+ this.textAreaElement.style.display = 'block';
641
+ this.textAreaElement.style.wordBreak = 'break-all';
642
+ this.textAreaElement.style.whiteSpace = 'pre-wrap';
643
+ this.textAreaElement.style.overflowWrap = 'break-word';
644
+ this.textAreaElement.style.left = `${leftTop.x}px`;
645
+ this.textAreaElement.style.top = `${leftTop.y}px`;
646
+ this.textAreaElement.style.width = `${size.x}px`;
647
+ this.textAreaElement.style.height = `${size.y}px`;
648
+ this.textAreaElement.style.lineHeight = `${property.lineHeight * viewportParam.scale}px`;
649
+ this.textAreaElement.style.letterSpacing = `${property.letterSpacing}px`;
650
+ this.textAreaElement.style.transformOrigin = 'center center';
651
+ this.textAreaElement.style.paintOrder = 'stroke fill';
652
+ if (property.rotation[2] !== undefined) {
653
+ this.textAreaElement.style.transform = `
654
+ rotateZ(${-property.rotation[2]}deg)
655
+ `;
656
+ }
657
+ this.textAreaElement.addEventListener('input', this.handleInput);
658
+ this.textAreaElement.addEventListener('blur', this.handleBlur);
659
+ window.setTimeout(() => {
660
+ if (this.interactionParam.textareaType === 'focus') {
661
+ this.textAreaElement.focus();
662
+ }
663
+ else {
664
+ this.textAreaElement.select();
665
+ }
666
+ }, 50);
667
+ }
668
+ else if (this.interactionParam.type === 'edit' && this.textAreaElement.style.display === 'block') {
669
+ const selectedItem = this._pageDataUtils.getSelectedItems()[0];
670
+ const viewportParam = this._pageDataUtils.interactionUtils.viewportParam;
671
+ const property = selectedItem.property;
672
+ const originBox = box.clone().rotate(property.rotation[2] * DEG2RAD);
673
+ const leftTop = originBox.min.clone();
674
+ const size = originBox.getSize();
675
+ this.textAreaElement.style.left = `${leftTop.x}px`;
676
+ this.textAreaElement.style.top = `${leftTop.y}px`;
677
+ this.textAreaElement.style.width = `${size.x}px`;
678
+ this.textAreaElement.style.height = `${size.y}px`;
679
+ this.textAreaElement.style.fontFamily = property.fontFamily;
680
+ this.textAreaElement.style.textAlign = this.getTextAlign(property.textAlign);
681
+ switch (SDK.config.mode) {
682
+ case 'editor': {
683
+ this.textAreaElement.style.fontSize = `${property.fontSize * viewportParam.scale}px`;
684
+ this.textAreaElement.style.lineHeight = `${property.lineHeight * viewportParam.scale}px`;
685
+ this.textAreaElement.style.transform = `rotateZ(${-property.rotation[2]}deg)`;
686
+ break;
687
+ }
688
+ case 'template': {
689
+ // debug code
690
+ const playerItem = this._pageDataUtils.getPlayerItemById(selectedItem.id);
691
+ const worldScale = new Vector3(1, 1, 1);
692
+ playerItem?.transform.getMatrix().decompose(new Vector3(), new Quaternion(), worldScale);
693
+ const testScale = playerItem.transform.scale;
694
+ const { size: [originWidth, originHeight], isPublishScene } = this._pageDataUtils.getTextOriginSize(selectedItem.id);
695
+ const shift = isPublishScene ? 2 : 0;
696
+ const pixelHeight = this._pageDataUtils.interactionUtils.getPixelSizeByWorldSize(new Vector3(0, originHeight, 0)).y;
697
+ const resultFontSize = property.fontSize / (property.lineHeight / pixelHeight) - shift * viewportParam.scale;
698
+ const scalar = new Vector2(testScale.x / originWidth, testScale.y / originHeight);
699
+ this.textAreaElement.style.left = `${leftTop.x + (scalar.x - 1) * size.x / scalar.x / 2}px`;
700
+ this.textAreaElement.style.top = `${leftTop.y + (scalar.y - 1) * size.y / scalar.y / 2}px`;
701
+ this.textAreaElement.style.width = `${size.x / scalar.x}px`;
702
+ this.textAreaElement.style.height = `${size.y / scalar.y}px`;
703
+ this.textAreaElement.style.transform = `scale(${scalar.x}, ${scalar.y}) rotateZ(${-property.rotation[2]}deg)`;
704
+ this.textAreaElement.style.fontSize = `${resultFontSize}px`;
705
+ this.textAreaElement.style.lineHeight = `${pixelHeight}px`;
706
+ break;
707
+ }
708
+ }
709
+ }
710
+ // 绘制自动布局指示线
711
+ this.renderAutoLayoutIndicator();
712
+ if (this.isAutoLayoutDragging && this.autoLayoutIndicator) {
713
+ result.push(this.autoLayoutGraphics);
714
+ }
715
+ result.push(this.graphics);
716
+ return result;
717
+ }
718
+ /**
719
+ * @description 绘制自动布局指示线
720
+ */
721
+ renderAutoLayoutIndicator() {
722
+ this.autoLayoutGraphics.clear();
723
+ if (!this.isAutoLayoutDragging || !this.autoLayoutIndicator) {
724
+ return;
725
+ }
726
+ const config = SDK.config.gestureHandlerConfig.autoLayoutIndicatorConfig;
727
+ const lineWidth = config?.lineWidth ?? 2;
728
+ const lineColor = config?.lineColor ?? 0x3b82f6;
729
+ // 绘制蓝色主指示线
730
+ this.autoLayoutGraphics.lineStyle(lineWidth, lineColor, 1);
731
+ this.autoLayoutGraphics.drawLine(this.autoLayoutIndicator.line);
732
+ // 绘制端点装饰(小圆点)
733
+ const start = this.autoLayoutIndicator.line.start;
734
+ const end = this.autoLayoutIndicator.line.end;
735
+ this.autoLayoutGraphics.beginFill(lineColor);
736
+ this.autoLayoutGraphics.drawCircle(start.x, start.y, 3);
737
+ this.autoLayoutGraphics.drawCircle(end.x, end.y, 3);
738
+ this.autoLayoutGraphics.endFill();
739
+ }
740
+ handleInput = async (e) => {
741
+ const textElm = e.target;
742
+ const selectedItem = this._pageDataUtils.getSelectedItems()[0];
743
+ const itemId = selectedItem.id;
744
+ const property = selectedItem.property;
745
+ await this._pageDataUtils.setItemProperty({
746
+ itemId,
747
+ type: SDKItemType.TEXT,
748
+ propertyName: 'text',
749
+ propertyValue: textElm.value
750
+ });
751
+ this._eventEmitter.emit('textInput', { itemId, text: textElm.value, fontFamily: property.fontFamily });
752
+ };
753
+ handleBlur = () => {
754
+ this.interactionParam.type = 'scale';
755
+ this.textAreaElement.style.display = 'none';
756
+ this.textAreaElement.removeEventListener('input', this.handleInput);
757
+ this.textAreaElement.removeEventListener('blur', this.handleBlur);
758
+ };
759
+ initInteractionPlane(viewportPoint) {
760
+ switch (this.interactionParam.type) {
761
+ case 'scale': {
762
+ if (this.result.type === 'empty') {
763
+ return;
764
+ }
765
+ const selectedItem = this._pageDataUtils.getSelectedItems().length == 1 ? this._pageDataUtils.getSelectedItems()[0] : undefined;
766
+ let anchor, itemPosition, itemRotation;
767
+ if (selectedItem) {
768
+ const itemTransform = this._pageDataUtils.getItemTransformById(selectedItem.id);
769
+ anchor = new Vector3().copyFrom(this._pageDataUtils.getPlayerItemById(selectedItem.id).transform.anchor).applyMatrix(itemTransform.matrix);
770
+ itemPosition = new Vector3().setFromMatrixPosition(itemTransform.matrix);
771
+ itemRotation = new Euler().setFromQuaternion(new Quaternion().setFromRotationMatrix(itemTransform.matrix));
772
+ }
773
+ this._pageDataUtils.interactionUtils.initInteractionPlane(itemPosition, itemRotation);
774
+ const scaleCorner = this.interactionParam.startPoint;
775
+ const { box } = this.result;
776
+ const farthestCorner = box.corners[0].clone();
777
+ let length = farthestCorner.distanceTo(scaleCorner);
778
+ box.corners.forEach(corner => {
779
+ if (corner.distanceTo(scaleCorner) > length) {
780
+ farthestCorner.copyFrom(corner);
781
+ length = corner.distanceTo(scaleCorner);
782
+ }
783
+ });
784
+ this.scaleParam = {
785
+ shift: new Vector3(),
786
+ center: new Vector3(),
787
+ corner: new Vector3(),
788
+ lastScalar: new Vector3(1, 1, 1),
789
+ farthestCorner
790
+ };
791
+ this.scaleParam.center = (anchor ?? itemPosition);
792
+ const point = this._pageDataUtils.getViewportLeftTopPosition();
793
+ if (scaleCorner) {
794
+ const viewportScaleCorner = scaleCorner.clone().subtract(point);
795
+ this.scaleParam.corner = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(viewportScaleCorner) ?? new Vector3();
796
+ }
797
+ this.scaleParam.shift = (this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(viewportPoint) ?? new Vector3()).subtract(this.scaleParam.corner);
798
+ break;
799
+ }
800
+ case 'width-scale':
801
+ case 'translation':
802
+ case 'rotation': {
803
+ this._pageDataUtils.interactionUtils.initInteractionPlane();
804
+ break;
805
+ }
806
+ }
807
+ }
808
+ /**
809
+ * @description 更新自动布局指示器
810
+ * 基于鼠标位置实时更新被拖拽元素的 layoutInfos(row 和 column)
811
+ * 注意:移动过程中只更新被拖拽元素的布局信息,其他元素保持不动
812
+ * 等到交互结束时再执行整体排布
813
+ */
814
+ updateAutoLayoutIndicator(item) {
815
+ const parentId = item.parentId;
816
+ if (!parentId) {
817
+ this.autoLayoutIndicator = null;
818
+ this.isAutoLayoutDragging = false;
819
+ return;
820
+ }
821
+ const parent = this._pageDataUtils.getSDKItem(parentId);
822
+ if (!isFrameItem(parent) || parent.layoutMode !== FrameLayoutMode.AUTO) {
823
+ this.autoLayoutIndicator = null;
824
+ this.isAutoLayoutDragging = false;
825
+ return;
826
+ }
827
+ this.isAutoLayoutDragging = true;
828
+ // 记录被拖拽元素的原始行列位置(如果还没记录)
829
+ if (this.autoLayoutDragOriginalPosition === null) {
830
+ const originalLayoutInfo = parent.property.layoutInfos?.[item.id];
831
+ this.autoLayoutDragOriginalPosition = {
832
+ row: originalLayoutInfo?.row ?? 0,
833
+ column: originalLayoutInfo?.column ?? 0,
834
+ };
835
+ }
836
+ // 获取画板中所有子元素(包含被拖拽的元素,用于计算正确的 insertIndex)
837
+ const allChildren = parent.children
838
+ .map(id => this._pageDataUtils.getSDKItem(id))
839
+ .filter((child) => child !== undefined);
840
+ // 计算基于位置的布局信息(row 和 column)
841
+ const layoutPosition = calculateAutoLayoutPositionByMouse(this.cursorPoint, allChildren, (id) => this._pageDataUtils.getViewBoxById(id), (child) => {
842
+ const parentItem = this._pageDataUtils.getSDKItem(child.parentId ?? '');
843
+ return isFrameItem(parentItem) ? parentItem.property.layoutInfos?.[child.id] : undefined;
844
+ }, item.id);
845
+ if (layoutPosition) {
846
+ // 获取被拖拽元素的原始行号(用于计算指示器)
847
+ const originalLayoutInfo = parent.property.layoutInfos?.[item.id];
848
+ const originalRow = originalLayoutInfo?.row ?? 0;
849
+ // 保存目标行列位置(延迟到 endAction 中执行实际修改)
850
+ this.pendingLayoutPosition = { ...layoutPosition };
851
+ // 计算指示器显示(基于鼠标位置直接计算)
852
+ const insertPosition = calculateInsertPositionFromLayout(this.cursorPoint, allChildren, item.id, parent, originalRow, (id) => this._pageDataUtils.getViewBoxById(id), () => {
853
+ const selectedItems = this._pageDataUtils.getSelectedItems();
854
+ const box = new Box2();
855
+ selectedItems.forEach(selectedItem => {
856
+ const targeBox = this._pageDataUtils.getViewBoxById(selectedItem.id);
857
+ box.union(targeBox);
858
+ });
859
+ return box;
860
+ });
861
+ if (insertPosition.siblingId) {
862
+ const indicatorLine = calculateAutoLayoutIndicatorLine(insertPosition, (id) => this._pageDataUtils.getViewBoxById(id));
863
+ this.autoLayoutIndicator = {
864
+ frameId: parent.id,
865
+ insertIndex: insertPosition.index,
866
+ insertDirection: insertPosition.direction,
867
+ targetSiblingId: insertPosition.siblingId,
868
+ line: indicatorLine,
869
+ targetRow: layoutPosition.row,
870
+ targetColumn: layoutPosition.column,
871
+ };
872
+ }
873
+ else {
874
+ // 没有找到参考元素,显示在末尾
875
+ this.autoLayoutIndicator = {
876
+ frameId: parent.id,
877
+ insertIndex: allChildren.length - 1,
878
+ insertDirection: 'after',
879
+ targetSiblingId: undefined,
880
+ line: new Line2(), // 空线
881
+ targetRow: layoutPosition.row,
882
+ targetColumn: layoutPosition.column,
883
+ };
884
+ }
885
+ }
886
+ else {
887
+ this.autoLayoutIndicator = null;
888
+ }
889
+ }
890
+ /**
891
+ * @description 执行自动布局重排序
892
+ * 基于更新后的 layoutInfos 重新排布所有元素
893
+ * @param draggedItem 被拖拽的元素
894
+ */
895
+ executeAutoLayoutReorder(draggedItem) {
896
+ if (!this.isAutoLayoutDragging) {
897
+ return;
898
+ }
899
+ // 获取拖拽的画板ID
900
+ const frameId = this.autoLayoutIndicator?.frameId;
901
+ if (!frameId) {
902
+ this.autoLayoutIndicator = null;
903
+ this.isAutoLayoutDragging = false;
904
+ this.autoLayoutDragOriginalPosition = null;
905
+ this.pendingLayoutPosition = null;
906
+ return;
907
+ }
908
+ const frameItem = this._pageDataUtils.getSDKItem(frameId);
909
+ if (!isFrameItem(frameItem)) {
910
+ this.autoLayoutIndicator = null;
911
+ this.isAutoLayoutDragging = false;
912
+ this.autoLayoutDragOriginalPosition = null;
913
+ this.pendingLayoutPosition = null;
914
+ return;
915
+ }
916
+ // 执行行列修改(延迟执行)
917
+ if (draggedItem && this.autoLayoutDragOriginalPosition && this.pendingLayoutPosition) {
918
+ // 调整其他元素的行列信息
919
+ adjustOtherElementsLayoutInfo(frameItem, draggedItem.id, this.autoLayoutDragOriginalPosition.row, this.autoLayoutDragOriginalPosition.column, this.pendingLayoutPosition.row, this.pendingLayoutPosition.column, (id) => this._pageDataUtils.getSDKItem(id));
920
+ // 更新被拖拽元素的 layoutInfo
921
+ updateItemLayoutInfo(draggedItem, frameItem, this.pendingLayoutPosition);
922
+ }
923
+ // 基于更新后的 layoutInfos 重新排布所有元素
924
+ this._pageDataUtils.applyFrameAutoLayout(frameId);
925
+ // 清理状态
926
+ this.autoLayoutIndicator = null;
927
+ this.isAutoLayoutDragging = false;
928
+ this.autoLayoutDragOriginalPosition = null;
929
+ this.pendingLayoutPosition = null;
930
+ }
931
+ getTextAlign(textAlign) {
932
+ switch (textAlign) {
933
+ case spec.TextAlignment.middle: return 'center';
934
+ case spec.TextAlignment.right: return 'right';
935
+ default: return 'left';
936
+ }
937
+ }
938
+ refreshFrameBoxes() {
939
+ this.frameBoxes.clear();
940
+ const frameItems = this._pageDataUtils.getPageData()?.items.filter(item => isFrameItem(item));
941
+ frameItems?.forEach(item => {
942
+ const viewBox = this._pageDataUtils.getViewBoxById(item.id);
943
+ this.frameBoxes.set(item.id, viewBox);
944
+ });
945
+ }
946
+ refreshItemFrameRelationShip(item, mouse) {
947
+ // 特效元素与画板元素不可移出画板
948
+ if (isFrameItem(item) || isEffectsItem(item)) {
949
+ return undefined;
950
+ }
951
+ const frameItems = this._pageDataUtils.getPageData()?.items.filter(item => isFrameItem(item)) ?? [];
952
+ const currentFrame = frameItems.find(frameItem => frameItem.children.includes(item.id));
953
+ const isFrameChild = !!currentFrame;
954
+ let targetFrameId = undefined;
955
+ this.frameBoxes.forEach((box, id) => {
956
+ if (box.containsPoint(mouse) && !targetFrameId) {
957
+ targetFrameId = id;
958
+ }
959
+ });
960
+ this.toAddedFrameId = targetFrameId;
961
+ // 若指向画布元素为undefined,且当前元素为画布子元素,则移出画布
962
+ if (!targetFrameId && isFrameChild) {
963
+ const toAddedItemIds = [item.id];
964
+ // 当元素为GroupItem时,需要将子元素移出画布,同时保持其父子级关系
965
+ if (isGroupItem(item)) {
966
+ const { children } = item.property;
967
+ toAddedItemIds.push(...children);
968
+ }
969
+ this._pageDataUtils.moveItemsOutOfFrame(toAddedItemIds);
970
+ if (item.parentId !== currentFrame.id && item.parentId) {
971
+ this._pageDataUtils.deleteItemParent(item.id, item.parentId);
972
+ }
973
+ }
974
+ // 当元素当前所处画布不为targetFrame,则将元素移入targetFrame
975
+ if (targetFrameId && (currentFrame?.id !== targetFrameId)) {
976
+ const toAddedItemIds = [item.id];
977
+ // 当元素为GroupItem时,需要将子元素移入画布,同时保持其父子级关系
978
+ if (isGroupItem(item)) {
979
+ const { children } = item.property;
980
+ toAddedItemIds.push(...children);
981
+ // 空组直接移入画板(保持原有流程)
982
+ if (children.length === 0) {
983
+ this._pageDataUtils.deleteItem(item.id);
984
+ }
985
+ else {
986
+ const frameBox = this.frameBoxes.get(targetFrameId);
987
+ if (!frameBox) {
988
+ return undefined;
989
+ }
990
+ const childrenInFrame = []; // 在画板内的子元素
991
+ const childrenOutOfFrame = []; // 在画板外的子元素
992
+ // 遍历所有子元素,使用相交检测判断位置
993
+ children.forEach(childId => {
994
+ const childBox = this._pageDataUtils.getViewBoxById(childId);
995
+ if (childBox && frameBox.intersectsBox(childBox)) {
996
+ childrenInFrame.push(childId);
997
+ }
998
+ else {
999
+ childrenOutOfFrame.push(childId);
1000
+ }
1001
+ });
1002
+ // 关键判断:没有任何子元素在画板内 → 组不进入画板
1003
+ if (childrenInFrame.length === 0) {
1004
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1005
+ console.log(`Group ${item.id} has no children in frame ${targetFrameId}, skipping frame entry.`);
1006
+ return undefined;
1007
+ }
1008
+ // 部分子元素在画板外 → 延迟处理(记录到 groupsNeedProcess)
1009
+ if (childrenOutOfFrame.length > 0) {
1010
+ console.log(`Group ${item.id}: ${childrenOutOfFrame.length} children out of frame, will process at drag end.`);
1011
+ this.groupsNeedProcess.set(item.id, {
1012
+ groupId: item.id,
1013
+ frameId: targetFrameId,
1014
+ childrenOutOfFrame: childrenOutOfFrame
1015
+ });
1016
+ }
1017
+ else {
1018
+ // 所有子元素都在画板内 → 清除之前的记录
1019
+ this.groupsNeedProcess.delete(item.id);
1020
+ }
1021
+ }
1022
+ }
1023
+ else if (item.parentId) {
1024
+ const parentItem = this._pageDataUtils.getSDKItem(item.parentId);
1025
+ if (parentItem && isGroupItem(parentItem)) {
1026
+ this._pageDataUtils.deleteItemParent(item.id, item.parentId);
1027
+ }
1028
+ }
1029
+ this._pageDataUtils.moveItemsToFrame(toAddedItemIds, targetFrameId);
1030
+ }
1031
+ return targetFrameId;
1032
+ }
1033
+ }