@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,1199 @@
1
+ import { Graphics, Text, Sprite } from '../../wireframe';
2
+ import { SDK } from '../../sdk';
3
+ import { spec, assertExist } from '../../shared';
4
+ import { Matrix4, Box2, DEG2RAD, getAbsMaxIndex, getAngleByVectors, getVector2Angle, isEqual, Line2, RAD2DEG, roundNumber, Vector2, Vector3, Euler, Circle } from '../../math';
5
+ import { Quaternion } from '../../math/quaternion';
6
+ import { Gizmo } from './gizmo';
7
+ import { SDKItemType } from '../../sdk-item/types';
8
+ import { isCardItem, isEffectsItem, isFrameItem, isGroupItem } from '../../sdk-item';
9
+ import { FrameLayoutMode } from '../../types';
10
+ import { processText } from '../utils';
11
+ import { calculateAutoLayoutIndicatorLine, calculateAutoLayoutPositionByMouse, calculateInsertPositionFromLayout, adjustOtherElementsLayoutInfo, updateItemLayoutInfo, } from '../../utils/layout-utils';
12
+ const nameTextStyle = {
13
+ fontSize: 14,
14
+ fill: 0x666666,
15
+ align: 'left',
16
+ };
17
+ const sizeTextStyle = {
18
+ fontSize: 14,
19
+ fill: 0x666666,
20
+ align: 'right',
21
+ };
22
+ export class TransformGizmo extends Gizmo {
23
+ type = 'transform';
24
+ /**
25
+ * 变换框绘制结果
26
+ */
27
+ wireframe;
28
+ /**
29
+ * 鼠标状态绘制结果
30
+ */
31
+ cursorResult = {
32
+ type: 'normal',
33
+ angle: 0,
34
+ };
35
+ /**
36
+ * 视图起始交互坐标[用于绘制指针]
37
+ */
38
+ cursorPoint = new Vector2();
39
+ /**
40
+ * 页面绘制 [通过页面绘制计算交互变换值,确保交互状态下移除页面也可以继续执行交互]
41
+ */
42
+ lastPoint = new Vector2();
43
+ /**
44
+ * @description 交互起点,用于判断是否可以触发选中交互
45
+ */
46
+ startPoint = new Vector2();
47
+ /**
48
+ * 单次旋转交互中z轴旋转角度[辅助计算鼠标位置]
49
+ */
50
+ rotationAngle = 0;
51
+ /**
52
+ * 是否处于激活状态
53
+ */
54
+ active = false;
55
+ /**
56
+ * 吸附对齐功能开关
57
+ */
58
+ enableAdsorption = true;
59
+ /**
60
+ * @description 缩放方向
61
+ */
62
+ scaleParam;
63
+ // 三维交互部分
64
+ /**
65
+ * 交互类型
66
+ */
67
+ activeType = 'null';
68
+ /**
69
+ * 上一帧鼠标射线碰撞位置
70
+ */
71
+ lastWorldPosition = new Vector3();
72
+ /**
73
+ * 是否忽视所有交互
74
+ */
75
+ _ignoreInteraction = false;
76
+ /**
77
+ * @description 初次点击鼠标位置 - 用于触发双击时,子元素选中
78
+ */
79
+ firstClickPoint = new Vector2();
80
+ /**
81
+ * @description 画板包围盒信息
82
+ */
83
+ frameBoxes = new Map();
84
+ /**
85
+ * @description
86
+ */
87
+ toAddedFrameId;
88
+ /**
89
+ * @description 自动布局插入指示器
90
+ */
91
+ autoLayoutIndicator = null;
92
+ /**
93
+ * @description 是否在自动布局拖拽中
94
+ */
95
+ isAutoLayoutDragging = false;
96
+ /**
97
+ * @description 被拖拽元素的原始行列位置(用于自动布局时调整其他元素)
98
+ */
99
+ autoLayoutDragOriginalPosition = null;
100
+ /**
101
+ * @description 待应用的布局位置(用于延迟执行行列修改)
102
+ */
103
+ pendingLayoutPosition = null;
104
+ /**
105
+ * @description 自动布局指示器 Graphics
106
+ */
107
+ autoLayoutGraphics = new Graphics();
108
+ /**
109
+ * @description 需要处理子元素分离的组信息
110
+ * key: groupId, value: 需要分离的子元素信息
111
+ */
112
+ groupsNeedProcess = new Map();
113
+ _pageDataUtils;
114
+ adsorptionGizmo;
115
+ selectorGizmo;
116
+ /**
117
+ * @description 选中控制器待触发
118
+ */
119
+ onSelectorGizmoTrigger = false;
120
+ /**
121
+ * @description Graphics to render transform gizmo result.
122
+ */
123
+ graphics = new Graphics();
124
+ /**
125
+ * @description 是否为锁定比例缩放
126
+ */
127
+ _isLockScale = true;
128
+ isShiftDown = false;
129
+ nameText = new Text('', nameTextStyle);
130
+ sizeText = new Text('', sizeTextStyle);
131
+ imageSprite;
132
+ groupSprite;
133
+ textSprite;
134
+ videoSprite;
135
+ frameSprite;
136
+ /**
137
+ * @description 卡片类型图标缓存(cardType → Sprite)
138
+ */
139
+ cardSprites = new Map();
140
+ effectsSprite;
141
+ get interactive() {
142
+ return SDK.config.gestureHandlerConfig.transformGizmoEnabled;
143
+ }
144
+ set interactive(bool) {
145
+ SDK.config.gestureHandlerConfig.transformGizmoEnabled = bool;
146
+ this.ignoreInteraction = bool;
147
+ }
148
+ get isLockScale() {
149
+ return this._isLockScale;
150
+ }
151
+ set isLockScale(state) {
152
+ if (state === this.isLockScale) {
153
+ return;
154
+ }
155
+ this._isLockScale = state;
156
+ }
157
+ get infoType() {
158
+ const selectedItem = this._pageDataUtils.getSelectedItems();
159
+ return selectedItem.length === 1 ? selectedItem[0].type : spec.ItemType.base;
160
+ }
161
+ constructor(utils, adsorptionGizmo, selectorGizmo) {
162
+ super();
163
+ this._pageDataUtils = utils;
164
+ this.selectorGizmo = selectorGizmo;
165
+ this.adsorptionGizmo = adsorptionGizmo;
166
+ this.wireframe = {
167
+ edges: [],
168
+ anchor: new Vector2(),
169
+ scaleCorners: [],
170
+ rotationCorners: [],
171
+ interactive: true,
172
+ cornerEnable: true,
173
+ directionEnable: true,
174
+ rotationEnabled: true,
175
+ scaleEnabled: true,
176
+ box: new Box2(),
177
+ totalBox: new Box2(),
178
+ childrenBoxes: [],
179
+ activeType: 'null',
180
+ interactiveDirection: new Vector2(),
181
+ scaleCorner: new Vector2(),
182
+ scaleEdgeCorners: [],
183
+ };
184
+ this.imageSprite = Sprite.from(SDK.config.gestureHandlerConfig.transformGizmoConfig.pictureLogoUrl);
185
+ this.imageSprite.width = 15;
186
+ this.imageSprite.height = 15;
187
+ this.groupSprite = Sprite.from(SDK.config.gestureHandlerConfig.transformGizmoConfig.groupLogoUrl);
188
+ this.groupSprite.width = 15;
189
+ this.groupSprite.height = 15;
190
+ this.textSprite = Sprite.from(SDK.config.gestureHandlerConfig.transformGizmoConfig.textLogoUrl);
191
+ this.textSprite.width = 15;
192
+ this.textSprite.height = 15;
193
+ this.videoSprite = Sprite.from(SDK.config.gestureHandlerConfig.transformGizmoConfig.videoLogoUrl);
194
+ this.videoSprite.width = 15;
195
+ this.videoSprite.height = 15;
196
+ this.frameSprite = Sprite.from(SDK.config.gestureHandlerConfig.transformGizmoConfig.frameLogoUrl);
197
+ this.frameSprite.width = 15;
198
+ this.frameSprite.height = 15;
199
+ this.effectsSprite = Sprite.from(SDK.config.gestureHandlerConfig.transformGizmoConfig.effectsLogoUrl);
200
+ this.effectsSprite.width = 15;
201
+ this.effectsSprite.height = 15;
202
+ }
203
+ /**
204
+ * @description 根据卡片类型获取对应的图标 Sprite,带缓存
205
+ * @param cardType 卡片类型标识
206
+ * @returns 对应的 Sprite 实例
207
+ */
208
+ getCardSprite(cardType) {
209
+ const cached = this.cardSprites.get(cardType);
210
+ if (cached) {
211
+ return cached;
212
+ }
213
+ const cardTypeConfig = SDK.config.itemConfig.cardConfig.cardTypes.find(t => t.type === cardType);
214
+ const iconUrl = cardTypeConfig?.iconUrl ?? SDK.config.gestureHandlerConfig.transformGizmoConfig.pictureLogoUrl;
215
+ const sprite = Sprite.from(iconUrl);
216
+ sprite.width = 15;
217
+ sprite.height = 15;
218
+ this.cardSprites.set(cardType, sprite);
219
+ return sprite;
220
+ }
221
+ get ignoreInteraction() {
222
+ return this._ignoreInteraction;
223
+ }
224
+ set ignoreInteraction(state) {
225
+ if (state === this._ignoreInteraction) {
226
+ return;
227
+ }
228
+ this._ignoreInteraction = state;
229
+ this.wireframe.cornerEnable = state;
230
+ }
231
+ actionKey(keyCode) {
232
+ this.isShiftDown = keyCode.shiftKey;
233
+ }
234
+ endActionKey(keyCode) {
235
+ this.isShiftDown = keyCode.shiftKey;
236
+ }
237
+ getRenderObjects() {
238
+ const result = [];
239
+ if (!this.interactive) {
240
+ return result;
241
+ }
242
+ this.refreshRenderObject();
243
+ result.push(this.graphics);
244
+ if (this.infoType !== spec.ItemType.base && SDK.config.gestureHandlerConfig.transformGizmoConfig.infoShowEnabled) {
245
+ result.push(this.nameText);
246
+ result.push(this.sizeText);
247
+ // result.push(this.nameText, this.sizeText)
248
+ switch (this.infoType) {
249
+ case SDKItemType.SPRITE:
250
+ result.push(this.imageSprite);
251
+ break;
252
+ case SDKItemType.CARD: {
253
+ const cardViewItem = this._pageDataUtils.getSelectedItems()[0];
254
+ result.push(this.getCardSprite(cardViewItem.cardType));
255
+ break;
256
+ }
257
+ case SDKItemType.GROUP:
258
+ result.push(this.groupSprite);
259
+ break;
260
+ case SDKItemType.TEXT:
261
+ if (!SDK.config.gestureHandlerConfig.textGizmoEnbaled) {
262
+ result.push(this.textSprite);
263
+ }
264
+ break;
265
+ case SDKItemType.VIDEO:
266
+ result.push(this.videoSprite);
267
+ break;
268
+ case SDKItemType.GENERATOR: {
269
+ const viewItem = this._pageDataUtils.getSelectedItems()[0];
270
+ if (viewItem.type === SDKItemType.GENERATOR && viewItem.generatorType === 'image') {
271
+ result.push(this.imageSprite);
272
+ }
273
+ else if (viewItem.type === SDKItemType.GENERATOR && viewItem.generatorType === 'video') {
274
+ result.push(this.videoSprite);
275
+ }
276
+ break;
277
+ }
278
+ case SDKItemType.EFFECTS: {
279
+ result.push(this.effectsSprite);
280
+ break;
281
+ }
282
+ case SDKItemType.FRAME: {
283
+ result.push(this.frameSprite);
284
+ break;
285
+ }
286
+ default:
287
+ break;
288
+ }
289
+ }
290
+ // 绘制自动布局指示线
291
+ this.renderAutoLayoutIndicator();
292
+ if (this.isAutoLayoutDragging && this.autoLayoutIndicator) {
293
+ result.push(this.autoLayoutGraphics);
294
+ }
295
+ return result;
296
+ }
297
+ /**
298
+ * @description 绘制自动布局指示线
299
+ */
300
+ renderAutoLayoutIndicator() {
301
+ this.autoLayoutGraphics.clear();
302
+ if (!this.isAutoLayoutDragging || !this.autoLayoutIndicator) {
303
+ return;
304
+ }
305
+ const config = SDK.config.gestureHandlerConfig.autoLayoutIndicatorConfig;
306
+ const lineWidth = config?.lineWidth ?? 2;
307
+ const lineColor = config?.lineColor ?? 0x3b82f6;
308
+ // 绘制蓝色主指示线
309
+ this.autoLayoutGraphics.lineStyle(lineWidth, lineColor, 1);
310
+ this.autoLayoutGraphics.drawLine(this.autoLayoutIndicator.line);
311
+ // 绘制端点装饰(小圆点)
312
+ const start = this.autoLayoutIndicator.line.start;
313
+ const end = this.autoLayoutIndicator.line.end;
314
+ this.autoLayoutGraphics.beginFill(lineColor);
315
+ this.autoLayoutGraphics.drawCircle(start.x, start.y, 3);
316
+ this.autoLayoutGraphics.drawCircle(end.x, end.y, 3);
317
+ this.autoLayoutGraphics.endFill();
318
+ }
319
+ cancelPreparation() {
320
+ this.active = false;
321
+ this.wireframe.activeType = 'null';
322
+ }
323
+ /**
324
+ * 行动准备事件 - 获取鼠标交互状态
325
+ * @param event 鼠标事件
326
+ */
327
+ preparationAction(event) {
328
+ if (!this.interactive) {
329
+ console.warn('Transform gizmo is not opened, use #SDK.openTransformGizmo() open this feature.');
330
+ return undefined;
331
+ }
332
+ const point = new Vector2(event.offsetX, event.offsetY);
333
+ const wireframe = this.wireframe;
334
+ this.refreshTransformType(point);
335
+ const { interactiveDirection } = wireframe;
336
+ this.refreshCursorResult(this.activeType, getVector2Angle(interactiveDirection));
337
+ if (this.activeType === 'translation') {
338
+ this.onSelectorGizmoTrigger = this.selectorGizmo.preparationAction(event, 'transform') === 'selector';
339
+ }
340
+ this.active = this.activeType !== 'null';
341
+ if (this.active) {
342
+ return this.type;
343
+ }
344
+ else {
345
+ return undefined;
346
+ }
347
+ }
348
+ /**
349
+ * 行动前置事件 - 交互预备事件
350
+ * @param event 鼠标事件
351
+ */
352
+ preAction(event, isDoubleClick = false) {
353
+ // 存储修改前的值
354
+ this._pageDataUtils.saveUndoRedoOldData();
355
+ this.active = event.buttons === 1;
356
+ this.startPoint = new Vector2(event.clientX, event.clientY);
357
+ this.lastPoint = new Vector2(event.clientX, event.clientY);
358
+ this.cursorPoint = new Vector2(event.offsetX, event.offsetY);
359
+ // 清空之前的状态记录,防止残留数据
360
+ this.groupsNeedProcess.clear();
361
+ this.refreshFrameBoxes();
362
+ // 非左键时不触发交互态
363
+ if (event.buttons !== 1) {
364
+ return undefined;
365
+ }
366
+ if (isDoubleClick) {
367
+ return this.selectorGizmo.preAction(event, isDoubleClick, new Vector2(event.offsetX, event.offsetY));
368
+ }
369
+ this.active = true;
370
+ if (this.wireframe.activeType === 'null') {
371
+ this.wireframe.activeType = 'translation';
372
+ }
373
+ const viewportPoint = this.cursorPoint.clone().subtract(this._pageDataUtils.getViewportLeftTopPosition());
374
+ this.initInteractionPlane(viewportPoint);
375
+ this.lastWorldPosition = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(viewportPoint) ?? new Vector3();
376
+ this.activeType = isDoubleClick ? 'null' : this.activeType;
377
+ return this.type;
378
+ }
379
+ /**
380
+ * 行动事件 - 变换交互
381
+ * @param event 鼠标事件
382
+ */
383
+ action(event) {
384
+ if (!this.interactive) {
385
+ console.warn('Transform gizmo is not opened, use #SDK.openTransformGizmo() open this feature.');
386
+ return undefined;
387
+ }
388
+ if (!this.active) {
389
+ this.active = true;
390
+ this.refreshWireframeBySelectedItems();
391
+ this.wireframe.activeType = 'translation';
392
+ this.lastPoint.set(event.clientX, event.clientY);
393
+ this.cursorPoint.set(event.offsetX, event.offsetY);
394
+ const viewportPoint = this.cursorPoint.clone().subtract(this._pageDataUtils.getViewportLeftTopPosition());
395
+ this.initInteractionPlane(viewportPoint);
396
+ this.lastWorldPosition = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(viewportPoint) ?? new Vector3();
397
+ // 清空之前的状态记录,防止残留数据
398
+ this.groupsNeedProcess.clear();
399
+ this.refreshFrameBoxes();
400
+ }
401
+ const currentPoint = new Vector2(event.clientX, event.clientY);
402
+ const shift = new Vector2().subtractVectors(currentPoint, this.lastPoint);
403
+ if (!this.wireframe.interactive || shift.length() <= 0) {
404
+ return;
405
+ }
406
+ const selectedItems = this._pageDataUtils.getSelectedItems();
407
+ // wireframe.refreshActiveType(shift, event.altKey, event.shiftKey);
408
+ const { anchor, activeType, interactiveDirection, scaleCorner } = this.wireframe;
409
+ const pageData = this._pageDataUtils.getPageData();
410
+ assertExist(pageData);
411
+ let cursorRotation = 0;
412
+ switch (activeType) {
413
+ // 旋转交互
414
+ case 'rotation': {
415
+ const center = anchor.clone();
416
+ const rotation = new Vector3();
417
+ const vecStart = new Vector2().subtractVectors(this.cursorPoint, center);
418
+ const vecCurrent = new Vector2().subtractVectors(this.cursorPoint.add(shift), center);
419
+ const angle = roundNumber(getAngleByVectors(vecStart, vecCurrent) * RAD2DEG, 3);
420
+ rotation.z = angle;
421
+ this.rotationAngle += angle;
422
+ cursorRotation = getVector2Angle(interactiveDirection) + this.rotationAngle * DEG2RAD;
423
+ selectedItems.forEach(item => {
424
+ this._pageDataUtils.rotateItem(item.id, rotation);
425
+ });
426
+ break;
427
+ }
428
+ case 'translation': {
429
+ // 平移
430
+ const move = shift.clone();
431
+ if (this.enableAdsorption) {
432
+ const targetBoxes = pageData.items.map(item => {
433
+ if (!selectedItems.find(si => si.id === item.id)
434
+ && selectedItems.filter(selectedItem => item.parentId && item.parentId === selectedItem.id).length === 0
435
+ && this._pageDataUtils.getItemEditEnable(item.id)
436
+ && !selectedItems.find(si => si.parentId === item.id)) {
437
+ return this._pageDataUtils.getViewBoxById(item.id);
438
+ }
439
+ else {
440
+ return new Box2();
441
+ }
442
+ }).filter(box => !box.isEmpty());
443
+ const preAbsorptionDistance = new Vector2(this.adsorptionGizmo.absorptionResult.x ?? 0, this.adsorptionGizmo.absorptionResult.y ?? 0);
444
+ this.adsorptionGizmo.boxTranslationAdsorption(move, this.wireframe.box, targetBoxes);
445
+ const absorptionDistance = new Vector2(this.adsorptionGizmo.absorptionResult.x ?? 0, this.adsorptionGizmo.absorptionResult.y ?? 0);
446
+ move.add(preAbsorptionDistance).subtract(absorptionDistance);
447
+ }
448
+ const worldPosition = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(this.cursorPoint.add(move).clone().subtract(this._pageDataUtils.getViewportLeftTopPosition()));
449
+ if (worldPosition) {
450
+ const translation = worldPosition.clone().subtract(this.lastWorldPosition);
451
+ selectedItems.forEach(item => {
452
+ translation.x = roundNumber(translation.x, 5);
453
+ translation.y = roundNumber(translation.y, 5);
454
+ translation.z = roundNumber(translation.z, 5);
455
+ this._pageDataUtils.moveItem(item.id, translation);
456
+ this.refreshItemFrameRelationShip(item, this.cursorPoint.clone());
457
+ });
458
+ this.lastWorldPosition.copyFrom(worldPosition);
459
+ }
460
+ break;
461
+ }
462
+ case 'scale': {
463
+ // 缩放
464
+ const resultViewPoint = this.cursorPoint.add(shift).clone().subtract(this._pageDataUtils.getViewportLeftTopPosition());
465
+ if (!scaleCorner || !this.scaleParam) {
466
+ return;
467
+ }
468
+ const selectedItem = selectedItems[0] ?? undefined;
469
+ // html卡片内容在非自动缩放的情况下,拖动的时候内容自适应,因此与智能画板一样,需要默认是自由缩放(即自由改大小)
470
+ const isSingleAutoFitContentCardItem = selectedItems.length === 1 && selectedItem && isCardItem(selectedItem) && !selectedItem.isAutoScale;
471
+ const isShiftLockScale = selectedItem && selectedItems.length === 1 && (isFrameItem(selectedItem) || isSingleAutoFitContentCardItem) ? !this.isShiftDown : this.isShiftDown;
472
+ const farthestCorner = this.scaleParam.farthestCorner;
473
+ const isValidScale = (scaleCorner.x - farthestCorner.x) * (this.cursorPoint.x - farthestCorner.x) > 0 && (scaleCorner.y - farthestCorner.y) * (this.cursorPoint.y - farthestCorner.y) > 0;
474
+ if (!isValidScale && this.isLockScale && !this.isShiftDown) {
475
+ resultViewPoint.x = farthestCorner.x + (scaleCorner.x - farthestCorner.x) / Math.abs(scaleCorner.x - farthestCorner.x) * 4;
476
+ resultViewPoint.y = farthestCorner.y + (scaleCorner.y - farthestCorner.y) / Math.abs(scaleCorner.y - farthestCorner.y) * 4;
477
+ }
478
+ const worldPosition = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(resultViewPoint);
479
+ if (worldPosition) {
480
+ const itemTransform = this._pageDataUtils.getItemTransformById(selectedItem?.id ?? '');
481
+ const rotationMatrix = new Matrix4().extractRotation(itemTransform.matrix).invert();
482
+ const { corner, center, shift: cornerShift, lastScalar } = this.scaleParam;
483
+ const startScale = new Vector3().subtractVectors(corner, center).applyMatrix(rotationMatrix);
484
+ const currentStartScale = startScale.clone().multiply(lastScalar);
485
+ const currentScale = worldPosition.clone().subtract(cornerShift).subtract(center).applyMatrix(rotationMatrix);
486
+ const scalar = new Vector3(1, 1, 1);
487
+ if (!this.isLockScale || isShiftLockScale) {
488
+ scalar.x = isEqual(currentStartScale.x, 0) ? 1 : currentScale.x / currentStartScale.x;
489
+ scalar.y = isEqual(currentStartScale.y, 0) ? 1 : currentScale.y / currentStartScale.y;
490
+ scalar.z = isEqual(currentStartScale.z, 0) ? 1 : currentScale.z / currentStartScale.z;
491
+ }
492
+ else {
493
+ const targetIndex = getAbsMaxIndex([
494
+ isEqual(startScale.x, 0) ? 1 : Math.abs(currentScale.x / startScale.x),
495
+ isEqual(startScale.y, 0) ? 1 : Math.abs(currentScale.y / startScale.y),
496
+ ]);
497
+ const currentLockScalar = currentScale.toArray()[targetIndex];
498
+ const targetScalar = currentStartScale.toArray()[targetIndex];
499
+ if (isEqual(lastScalar.x, lastScalar.y) && isEqual(lastScalar.z, lastScalar.x)) {
500
+ const resultScalar = isEqual(targetScalar, 0) ? 1 : Math.abs(currentLockScalar / targetScalar);
501
+ scalar.set(resultScalar, resultScalar, resultScalar);
502
+ }
503
+ else {
504
+ if (targetIndex === 0) {
505
+ scalar.x = isEqual(targetScalar, 0) ? 1 : Math.abs(currentLockScalar / targetScalar);
506
+ const resultScalarY = startScale.y * currentLockScalar / startScale.x;
507
+ scalar.y = isEqual(currentStartScale.y, 0) ? 1 : resultScalarY / currentStartScale.y;
508
+ const resultScalarZ = startScale.z * currentLockScalar / startScale.x;
509
+ scalar.z = isEqual(currentStartScale.z, 0) ? 1 : resultScalarZ / currentStartScale.z;
510
+ }
511
+ else if (targetIndex === 1) {
512
+ scalar.y = isEqual(targetScalar, 0) ? 1 : Math.abs(currentLockScalar / targetScalar);
513
+ const resultScalarX = startScale.x * currentLockScalar / startScale.y;
514
+ scalar.x = isEqual(currentStartScale.x, 0) ? 1 : resultScalarX / currentStartScale.x;
515
+ const resultScalarZ = startScale.z * currentLockScalar / startScale.y;
516
+ scalar.z = isEqual(currentStartScale.z, 0) ? 1 : resultScalarZ / currentStartScale.z;
517
+ }
518
+ else {
519
+ scalar.z = isEqual(targetScalar, 0) ? 1 : Math.abs(currentLockScalar / targetScalar);
520
+ const resultScalarX = startScale.x * currentLockScalar / startScale.z;
521
+ scalar.x = isEqual(currentStartScale.x, 0) ? 1 : resultScalarX / currentStartScale.x;
522
+ const resultScalarY = startScale.y * currentLockScalar / startScale.z;
523
+ scalar.y = isEqual(currentStartScale.y, 0) ? 1 : resultScalarY / currentStartScale.y;
524
+ }
525
+ }
526
+ }
527
+ // 计算 resultScalar 和位移补偿
528
+ const resultScalar = scalar.clone().subtract(new Vector3(1, 1, 0)).divide(2).add(new Vector3(1, 1, 0.5));
529
+ const originCorner = center.clone().add(new Vector3().subtractVectors(corner, center).multiply(new Vector3(lastScalar.x, lastScalar.y, 1)));
530
+ this.scaleParam.lastScalar.multiply(resultScalar);
531
+ const currentCorner = center.clone().add(new Vector3().subtractVectors(corner, center).multiply(new Vector3(this.scaleParam.lastScalar.x, this.scaleParam.lastScalar.y, 1)));
532
+ const translation = new Vector3().subtractVectors(currentCorner, originCorner);
533
+ translation.x = roundNumber(translation.x, 5);
534
+ translation.y = roundNumber(translation.y, 5);
535
+ translation.z = roundNumber(translation.z, 5);
536
+ this.scaleParam.center.add(translation);
537
+ this.scaleParam.corner.add(translation);
538
+ // 判断是否为 FrameItem,如果是则走尺寸调整逻辑
539
+ if (selectedItem && isFrameItem(selectedItem)) {
540
+ const currentWidth = selectedItem.pixelWidth;
541
+ const currentHeight = selectedItem.pixelHeight;
542
+ const newWidth = currentWidth * resultScalar.x;
543
+ const newHeight = currentHeight * resultScalar.y;
544
+ this._pageDataUtils.resizeFrameItem(selectedItem.id, [newWidth, newHeight], translation);
545
+ }
546
+ else if (isSingleAutoFitContentCardItem && selectedItem && isCardItem(selectedItem)) {
547
+ const currentWidth = selectedItem.pixelWidth;
548
+ const currentHeight = selectedItem.pixelHeight;
549
+ const newWidth = currentWidth * resultScalar.x;
550
+ const newHeight = currentHeight * resultScalar.y;
551
+ this._pageDataUtils.resizeCardItem(selectedItem.id, [newWidth, newHeight], translation);
552
+ void this._pageDataUtils.setItemProperty({
553
+ itemId: selectedItem.id,
554
+ type: SDKItemType.CARD,
555
+ property: {
556
+ width: newWidth,
557
+ height: newHeight,
558
+ },
559
+ });
560
+ }
561
+ else {
562
+ // 普通元素的缩放逻辑
563
+ selectedItems.forEach(item => {
564
+ this._pageDataUtils.scaleItem(item.id, resultScalar);
565
+ this._pageDataUtils.moveItem(item.id, translation);
566
+ });
567
+ }
568
+ this.lastWorldPosition.copyFrom(worldPosition);
569
+ }
570
+ cursorRotation = getVector2Angle(interactiveDirection);
571
+ break;
572
+ }
573
+ }
574
+ // 检查是否是自动布局画板的子元素
575
+ selectedItems.forEach(item => {
576
+ this.updateAutoLayoutIndicator(item);
577
+ });
578
+ this.refreshCursorResult(activeType, cursorRotation);
579
+ this.lastPoint.copyFrom(currentPoint);
580
+ }
581
+ /**
582
+ * 行动结束时间 - 交互结束
583
+ * @param event 鼠标事件
584
+ */
585
+ endAction(event) {
586
+ // 执行自动布局重排序(如果在自动布局拖拽中)
587
+ const selectedItems = this._pageDataUtils.getSelectedItems();
588
+ const draggedItem = selectedItems.length === 1 ? selectedItems[0] : undefined;
589
+ this.executeAutoLayoutReorder(draggedItem);
590
+ // 推入 undo / redo 栈
591
+ this._pageDataUtils.pushUndoRedoData();
592
+ this.active = false;
593
+ const { clientX, clientY, offsetX, offsetY } = event;
594
+ const currentPoint = new Vector2(clientX, clientY);
595
+ if (currentPoint.distanceTo(this.startPoint) < 4 && this.onSelectorGizmoTrigger) {
596
+ const selectedIds = this.selectorGizmo.hitTest(new Vector2(offsetX, offsetY));
597
+ if (!this.isShiftDown) {
598
+ this._pageDataUtils.clearSelectedItems(selectedIds.length === 0);
599
+ }
600
+ this.selectorGizmo.addSelectedItem(selectedIds[0]);
601
+ }
602
+ else {
603
+ const shift = new Vector2().subtractVectors(currentPoint, this.lastPoint);
604
+ this.cursorPoint.add(shift);
605
+ const wireframe = this.wireframe;
606
+ if (wireframe.activeType === 'scale') {
607
+ this.refreshFrameChildren();
608
+ }
609
+ // 根据当前鼠标位置重新计算结果
610
+ this.refreshTransformType(this.cursorPoint);
611
+ const { activeType, interactiveDirection } = wireframe;
612
+ this.refreshCursorResult(activeType, getVector2Angle(interactiveDirection));
613
+ this.adsorptionGizmo.clearAbsorptionResult();
614
+ }
615
+ // 处理需要断开父子关系的组
616
+ this.processGroupsNeedProcess();
617
+ this.toAddedFrameId = undefined;
618
+ this.frameBoxes.clear();
619
+ this.onSelectorGizmoTrigger = false;
620
+ this.rotationAngle = 0;
621
+ this.lastPoint = new Vector2();
622
+ this.startPoint = new Vector2();
623
+ this.cursorPoint = new Vector2();
624
+ return undefined;
625
+ }
626
+ /**
627
+ * 交互中断事件 - 清空交互状态以及鼠标状态, 重新进行交互Gizmo选中
628
+ * @returns undefined
629
+ */
630
+ interruption() {
631
+ this.active = false;
632
+ this.cursorResult.type = 'normal';
633
+ this.adsorptionGizmo.clearAbsorptionResult();
634
+ // 清空需要处理的组记录(交互被打断时不执行断开操作)
635
+ if (this.groupsNeedProcess.size > 0) {
636
+ console.log('Clearing groupsNeedProcess due to interruption.');
637
+ this.groupsNeedProcess.clear();
638
+ }
639
+ // 清空自动布局相关状态(交互被打断时不执行行列修改)
640
+ if (this.isAutoLayoutDragging) {
641
+ this.autoLayoutIndicator = null;
642
+ this.isAutoLayoutDragging = false;
643
+ this.autoLayoutDragOriginalPosition = null;
644
+ this.pendingLayoutPosition = null;
645
+ }
646
+ return undefined;
647
+ }
648
+ /**
649
+ * 刷新指针结果
650
+ * @param activeType 交互类型
651
+ * @param point 指针位置
652
+ * @param dir 指针方向
653
+ */
654
+ refreshCursorResult(activeType, angle = 0) {
655
+ switch (activeType) {
656
+ case 'scale': {
657
+ this.cursorResult = {
658
+ type: 'scale',
659
+ angle: (angle + Math.PI / 4) * RAD2DEG,
660
+ };
661
+ break;
662
+ }
663
+ case 'rotation': {
664
+ this.cursorResult = {
665
+ type: 'rotation',
666
+ angle: (angle + Math.PI / 4) * RAD2DEG,
667
+ };
668
+ break;
669
+ }
670
+ case 'translation':
671
+ case 'null': {
672
+ this.cursorResult = {
673
+ type: 'normal',
674
+ angle: 0,
675
+ };
676
+ break;
677
+ }
678
+ }
679
+ }
680
+ /**
681
+ * 根据选中元素刷新交互框
682
+ */
683
+ refreshResults() {
684
+ const lastActiveType = this.wireframe.activeType;
685
+ this.refreshWireframeBySelectedItems();
686
+ if (lastActiveType !== this.wireframe.activeType) {
687
+ this.refreshCursorResult(this.wireframe.activeType);
688
+ }
689
+ }
690
+ initInteractionPlane(viewportPoint) {
691
+ const selectedItems = this._pageDataUtils.getSelectedItems();
692
+ switch (this.wireframe.activeType) {
693
+ case 'scale': {
694
+ const selectedItem = selectedItems.length == 1 ? selectedItems[0] : undefined;
695
+ let anchor, itemPosition, itemRotation;
696
+ if (selectedItem) {
697
+ const itemTransform = this._pageDataUtils.getItemTransformById(selectedItem.id);
698
+ anchor = new Vector3().copyFrom(this._pageDataUtils.getPlayerItemById(selectedItem.id).transform.anchor).applyMatrix(itemTransform.matrix);
699
+ itemPosition = new Vector3().setFromMatrixPosition(itemTransform.matrix);
700
+ itemRotation = new Euler().setFromQuaternion(new Quaternion().setFromRotationMatrix(itemTransform.matrix));
701
+ }
702
+ this._pageDataUtils.interactionUtils.initInteractionPlane(itemPosition, itemRotation);
703
+ const { scaleCorner, box } = this.wireframe;
704
+ const farthestCorner = box.corners[0].clone();
705
+ let length = farthestCorner.distanceTo(scaleCorner);
706
+ box.corners.forEach(corner => {
707
+ if (corner.distanceTo(scaleCorner) > length) {
708
+ farthestCorner.copyFrom(corner);
709
+ length = corner.distanceTo(scaleCorner);
710
+ }
711
+ });
712
+ this.scaleParam = {
713
+ shift: new Vector3(),
714
+ center: new Vector3(),
715
+ corner: new Vector3(),
716
+ lastScalar: new Vector3(1, 1, 1),
717
+ farthestCorner
718
+ };
719
+ this.scaleParam.center = (anchor ?? itemPosition);
720
+ const point = this._pageDataUtils.getViewportLeftTopPosition();
721
+ if (['scale'].includes(this.wireframe.activeType) && this.wireframe.scaleCorner) {
722
+ const viewportScaleCorner = this.wireframe.scaleCorner.clone().subtract(point);
723
+ this.scaleParam.corner = this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(viewportScaleCorner) ?? new Vector3();
724
+ }
725
+ this.scaleParam.shift = (this._pageDataUtils.interactionUtils.getWorldPositionByViewPoint(viewportPoint) ?? new Vector3()).subtract(this.scaleParam.corner);
726
+ break;
727
+ }
728
+ case 'translation':
729
+ case 'rotation': {
730
+ this._pageDataUtils.interactionUtils.initInteractionPlane();
731
+ break;
732
+ }
733
+ }
734
+ }
735
+ refreshRenderObject() {
736
+ this.graphics.clear();
737
+ this.nameText.text = '';
738
+ this.sizeText.text = '';
739
+ const { wireframeAlpha, wireframeColor, wireframeWidth, cornerFillColor, cornerLineWidth, cornerLineColor, cornerLineAlpha, infoShowEnabled, sizeTextColor, nameTextColor } = SDK.config.gestureHandlerConfig.transformGizmoConfig;
740
+ if (!this.wireframe.box.isEmpty()) {
741
+ // line draw
742
+ this.graphics.lineStyle(wireframeWidth, wireframeColor, wireframeAlpha);
743
+ this.wireframe.edges.forEach(edge => {
744
+ this.graphics.drawLine(edge);
745
+ });
746
+ const drawNonOrthogonalBox = (box) => {
747
+ box.corners.forEach((corner, index) => {
748
+ const line = new Line2(corner, box.corners[(index + 1) % 4]);
749
+ this.graphics.drawLine(line);
750
+ });
751
+ };
752
+ // children draw
753
+ this.wireframe.childrenBoxes.forEach(box => {
754
+ drawNonOrthogonalBox(box);
755
+ });
756
+ // corner draw
757
+ if (this.wireframe.cornerEnable) {
758
+ this.graphics.beginFill(cornerFillColor);
759
+ this.graphics.lineStyle(cornerLineWidth, cornerLineColor, cornerLineAlpha);
760
+ this.wireframe.scaleCorners.forEach(corner => {
761
+ this.graphics.drawCircle(corner.center.x, corner.center.y, corner.radius);
762
+ });
763
+ this.graphics.endFill();
764
+ }
765
+ // info draw
766
+ if (this.infoType !== spec.ItemType.base && infoShowEnabled) {
767
+ const selectedItems = this._pageDataUtils.getSelectedItems();
768
+ const viewItem = selectedItems[0];
769
+ const name = selectedItems.length === 1 ? viewItem.name : '';
770
+ const { rotation } = viewItem.property;
771
+ const { pixelWidth, pixelHeight } = viewItem;
772
+ const size = [pixelWidth, pixelHeight];
773
+ const activeSprite = this.infoType === SDKItemType.GROUP ? this.groupSprite
774
+ : this.infoType === SDKItemType.SPRITE ? this.imageSprite
775
+ : this.infoType === SDKItemType.CARD ? this.getCardSprite(viewItem.cardType)
776
+ : this.infoType === SDKItemType.TEXT ? this.textSprite
777
+ : this.infoType === SDKItemType.VIDEO ? this.videoSprite
778
+ : this.infoType === SDKItemType.GENERATOR && viewItem.generatorType === 'image' ? this.imageSprite
779
+ : this.infoType === SDKItemType.GENERATOR && viewItem.generatorType === 'video' ? this.videoSprite
780
+ : this.infoType === SDKItemType.EFFECTS ? this.effectsSprite
781
+ : this.infoType === SDKItemType.FRAME ? this.frameSprite
782
+ : this.imageSprite;
783
+ const corner = this.wireframe.box.corners[3];
784
+ const resultRotation = rotation[2] * DEG2RAD;
785
+ activeSprite.anchor.set(0, 1);
786
+ activeSprite.x = corner.x;
787
+ activeSprite.y = corner.y - 5;
788
+ activeSprite.rotation = -resultRotation;
789
+ const { x: viewBoxWidth } = this.wireframe.box.getSize();
790
+ const namePosition = corner.clone().add(new Vector2(20, 0)).rotateAround(corner, -resultRotation);
791
+ const startSizePoint = this.wireframe.box.corners[0].clone().subtract(new Vector2(0, 20));
792
+ const resultSizePoint = startSizePoint.rotateAround(this.wireframe.box.corners[0].clone(), -resultRotation);
793
+ this.sizeText.text = size.map(s => roundNumber(s, 0)).join(' * ');
794
+ this.sizeText.style.fill = sizeTextColor;
795
+ this.sizeText.anchor.set(1, 0);
796
+ this.sizeText.x = resultSizePoint.x;
797
+ this.sizeText.y = resultSizePoint.y;
798
+ this.sizeText.rotation = -resultRotation;
799
+ const { width: sizeTextWidth } = this.sizeText.getBounds();
800
+ if (viewBoxWidth < sizeTextWidth * 2) {
801
+ this.sizeText.text = '';
802
+ }
803
+ const nameText = ` ${name}`;
804
+ const rotationShift = new Vector2(0, 4).rotateAround(new Vector2(), resultRotation);
805
+ this.nameText.text = nameText;
806
+ this.nameText.style.fill = nameTextColor;
807
+ this.nameText.anchor.set(0, 1);
808
+ this.nameText.x = namePosition.x + rotationShift.x;
809
+ this.nameText.y = namePosition.y - rotationShift.y;
810
+ this.nameText.rotation = -resultRotation;
811
+ const { width: nameTextWidth } = this.nameText.getBounds();
812
+ if ((20 + nameTextWidth + sizeTextWidth) > viewBoxWidth) {
813
+ if (viewBoxWidth < sizeTextWidth * 2) {
814
+ this.sizeText.text = '';
815
+ this.nameText.text = processText(nameText, nameTextStyle, viewBoxWidth - 20, 1);
816
+ }
817
+ else {
818
+ const nameTextWidth = viewBoxWidth - 20 - sizeTextWidth - 5;
819
+ this.nameText.text = processText(nameText, nameTextStyle, nameTextWidth, 1);
820
+ }
821
+ }
822
+ }
823
+ // to added frame wireframe
824
+ if (this.toAddedFrameId) {
825
+ const targetFrameBox = this.frameBoxes.get(this.toAddedFrameId);
826
+ if (targetFrameBox) {
827
+ this.graphics.lineStyle(wireframeWidth, wireframeColor, wireframeAlpha);
828
+ this.graphics.drawBox(targetFrameBox);
829
+ }
830
+ }
831
+ }
832
+ }
833
+ refreshTransformType(point) {
834
+ // 位于交互范围内
835
+ this.activeType = 'null';
836
+ let continueCheck = true;
837
+ this.wireframe.interactiveDirection = new Vector2();
838
+ this.wireframe.scaleCorner = undefined;
839
+ this.wireframe.scaleEdgeCorners = undefined;
840
+ if (!this.wireframe.interactive) {
841
+ return;
842
+ }
843
+ if (continueCheck && this.wireframe.totalBox.containsPoint(point)) {
844
+ if (this.wireframe.scaleEnabled) {
845
+ // 判断角缩放交互
846
+ for (let i = 0, len = this.wireframe.scaleCorners.length; i < len; i++) {
847
+ const state = this.wireframe.scaleCorners[i].containsPoint(point);
848
+ if (state) {
849
+ continueCheck = false;
850
+ this.activeType = 'scale';
851
+ this.wireframe.activeType = 'scale';
852
+ this.wireframe.scaleCorner = this.wireframe.scaleCorners[i].center.clone();
853
+ this.wireframe.interactiveDirection.copyFrom(this.wireframe.scaleCorners[i].center).subtract(this.wireframe.box.getCenter());
854
+ break;
855
+ }
856
+ }
857
+ }
858
+ // 判断平移交互
859
+ if (continueCheck && this.wireframe.box.containsPoint(point, false)) {
860
+ continueCheck = false;
861
+ this.activeType = 'translation';
862
+ this.wireframe.activeType = 'translation';
863
+ }
864
+ // 判断角旋转交互
865
+ if (this.wireframe.rotationEnabled) {
866
+ for (let i = 0, len = this.wireframe.scaleCorners.length; i < len; i++) {
867
+ const state = this.wireframe.rotationCorners[i].containsPoint(point);
868
+ if (state && continueCheck) {
869
+ continueCheck = false;
870
+ this.activeType = 'rotation';
871
+ this.wireframe.activeType = 'rotation';
872
+ this.wireframe.interactiveDirection.copyFrom(this.wireframe.rotationCorners[i].center).subtract(this.wireframe.box.getCenter());
873
+ break;
874
+ }
875
+ }
876
+ }
877
+ }
878
+ }
879
+ refreshWireframeBySelectedItems() {
880
+ const selectedItems = this._pageDataUtils.getSelectedItems();
881
+ const isTemplateMode = SDK.config.mode === 'template';
882
+ const selectedItem = selectedItems.length === 1 ? selectedItems[0] : undefined;
883
+ const isSingleCardItem = isCardItem(selectedItem);
884
+ const isGeneratorItem = selectedItem?.type === SDKItemType.GENERATOR;
885
+ const isAutoLayoutFrame = selectedItem?.type === SDKItemType.FRAME && selectedItem?.layoutMode === FrameLayoutMode.AUTO;
886
+ const canScale = selectedItems.length === 1 && selectedItem?.type !== SDKItemType.GROUP && !isAutoLayoutFrame && !isGeneratorItem;
887
+ const box = new Box2();
888
+ this.wireframe.edges = [];
889
+ this.wireframe.scaleCorners = [];
890
+ this.wireframe.childrenBoxes = [];
891
+ this.wireframe.rotationCorners = [];
892
+ this.wireframe.rotationEnabled = isTemplateMode;
893
+ this.wireframe.scaleEnabled = canScale || isTemplateMode;
894
+ this.wireframe.cornerEnable = canScale || isTemplateMode;
895
+ if (selectedItems.length > 1) {
896
+ selectedItems.forEach(item => {
897
+ if (this._pageDataUtils.getItemShow(item.id)) {
898
+ const itemViewBox = this._pageDataUtils.getViewBoxById(item.id);
899
+ box.union(itemViewBox);
900
+ this.wireframe.childrenBoxes.push(new Box2().copyFrom(itemViewBox).expandByScalar(1));
901
+ }
902
+ });
903
+ this.wireframe.anchor = box.getCenter();
904
+ this.wireframe.interactive = true;
905
+ }
906
+ else if (selectedItems.length === 1) {
907
+ const item = selectedItems[0];
908
+ if ((item.type !== SDKItemType.TEXT) && this._pageDataUtils.getItemShow(item.id)) {
909
+ const itemViewBox = this._pageDataUtils.getViewBoxById(item.id);
910
+ box.copyFrom(itemViewBox);
911
+ this.wireframe.anchor = this._pageDataUtils.getItemViewAnchor(item.id) ?? box.getCenter();
912
+ this.wireframe.interactive = this._pageDataUtils.getItemEditEnable(item.id);
913
+ }
914
+ }
915
+ const isBoxEmpty = box.isEmpty();
916
+ const boxExpandScalar = isBoxEmpty ? 0 : 1;
917
+ const totalBoxExpandScalar = isBoxEmpty ? 0 : 16;
918
+ this.wireframe.activeType = !selectedItems.length ? 'null' : this.wireframe.activeType;
919
+ this.wireframe.box.copyFrom(box).expandByScalar(boxExpandScalar);
920
+ this.wireframe.totalBox.copyFrom(box).expandByScalar(totalBoxExpandScalar);
921
+ // 根据box获取边缘线
922
+ const corners = this.wireframe.box.corners;
923
+ if (!isSingleCardItem) {
924
+ corners.forEach((corner, i) => {
925
+ this.wireframe.edges.push(new Line2(new Vector2().copyFrom(corner), new Vector2().copyFrom(corners[(i + 1) % 4])));
926
+ });
927
+ }
928
+ const { scaleCircleSize, rotationCircleSize, } = SDK.config.gestureHandlerConfig.transformGizmoConfig;
929
+ // 成组暂不支持缩放
930
+ if (selectedItems.length === 1) {
931
+ this.wireframe.box.corners.forEach(corner => {
932
+ // 缩放Circle
933
+ this.wireframe.scaleCorners.push(new Circle(corner, scaleCircleSize));
934
+ // 旋转Circle
935
+ const dir = new Vector2().subtractVectors(corner, this.wireframe.box.getCenter());
936
+ const center = new Vector2().copyFrom(this.wireframe.box.getCenter()).add(new Vector2().copyFrom(dir).normalize().multiply(dir.length() + rotationCircleSize - scaleCircleSize));
937
+ this.wireframe.rotationCorners.push(new Circle(center, rotationCircleSize));
938
+ });
939
+ }
940
+ }
941
+ setAdsorptionEnabled(state) {
942
+ this.enableAdsorption = state;
943
+ }
944
+ /**
945
+ * @description 处理需要断开父子关系的组
946
+ * 在拖拽结束时调用,将画板外的子元素从组中移除,并移出画板
947
+ */
948
+ processGroupsNeedProcess() {
949
+ if (this.groupsNeedProcess.size === 0) {
950
+ return;
951
+ }
952
+ this.groupsNeedProcess.forEach(({ groupId, frameId, childrenOutOfFrame }) => {
953
+ const groupItem = this._pageDataUtils.getSDKItem(groupId);
954
+ if (!groupItem || !isGroupItem(groupItem)) {
955
+ return;
956
+ }
957
+ // 1. 从组中移除画板外的子元素
958
+ groupItem.property.children = groupItem.property.children.filter(childId => !childrenOutOfFrame.includes(childId));
959
+ // 2. 清除子元素的 parentId
960
+ childrenOutOfFrame.forEach(childId => {
961
+ const childItem = this._pageDataUtils.getSDKItem(childId);
962
+ if (childItem) {
963
+ childItem.parentId = undefined;
964
+ }
965
+ });
966
+ // 3. 将不在画板内的子元素移出画板(移回主合成)
967
+ if (childrenOutOfFrame.length > 0 && frameId) {
968
+ console.log(`Moving ${childrenOutOfFrame.length} children out of frame ${frameId}`);
969
+ this._pageDataUtils.moveItemsOutOfFrame(childrenOutOfFrame);
970
+ }
971
+ });
972
+ this.groupsNeedProcess.clear();
973
+ }
974
+ refreshFrameBoxes() {
975
+ this.frameBoxes.clear();
976
+ const frameItems = this._pageDataUtils.getPageData()?.items.filter(item => isFrameItem(item));
977
+ frameItems?.forEach(item => {
978
+ const viewBox = this._pageDataUtils.getViewBoxById(item.id);
979
+ this.frameBoxes.set(item.id, viewBox);
980
+ });
981
+ }
982
+ refreshItemFrameRelationShip(item, mouse) {
983
+ // 特效元素与画板元素不可移出画板
984
+ if (isFrameItem(item) || isEffectsItem(item)) {
985
+ return undefined;
986
+ }
987
+ const frameItems = this._pageDataUtils.getPageData()?.items.filter(item => isFrameItem(item)) ?? [];
988
+ const currentFrame = frameItems.find(frameItem => frameItem.children.includes(item.id));
989
+ const isFrameChild = !!currentFrame;
990
+ let targetFrameId = undefined;
991
+ this.frameBoxes.forEach((box, id) => {
992
+ if (box.containsPoint(mouse) && !targetFrameId) {
993
+ targetFrameId = id;
994
+ }
995
+ });
996
+ this.toAddedFrameId = targetFrameId;
997
+ // 若指向画布元素为undefined,且当前元素为画布子元素,则移出画布
998
+ if (!targetFrameId && isFrameChild) {
999
+ const toAddedItemIds = [item.id];
1000
+ // 当元素为GroupItem时,需要将子元素移出画布,同时保持其父子级关系
1001
+ if (isGroupItem(item)) {
1002
+ const { children } = item.property;
1003
+ toAddedItemIds.push(...children);
1004
+ }
1005
+ this._pageDataUtils.moveItemsOutOfFrame(toAddedItemIds);
1006
+ if (item.parentId !== currentFrame.id && item.parentId) {
1007
+ this._pageDataUtils.deleteItemParent(item.id, item.parentId);
1008
+ }
1009
+ }
1010
+ // 当元素当前所处画布不为targetFrame,则将元素移入targetFrame
1011
+ if (targetFrameId && (currentFrame?.id !== targetFrameId)) {
1012
+ const toAddedItemIds = [item.id];
1013
+ // 当元素为GroupItem时,需要将子元素移入画布,同时保持其父子级关系
1014
+ if (isGroupItem(item)) {
1015
+ const { children } = item.property;
1016
+ toAddedItemIds.push(...children);
1017
+ // 空组直接移入画板(保持原有流程)
1018
+ if (children.length === 0) {
1019
+ this._pageDataUtils.deleteItem(item.id);
1020
+ }
1021
+ else {
1022
+ const frameBox = this.frameBoxes.get(targetFrameId);
1023
+ if (!frameBox) {
1024
+ return undefined;
1025
+ }
1026
+ const childrenInFrame = []; // 在画板内的子元素
1027
+ const childrenOutOfFrame = []; // 在画板外的子元素
1028
+ // 遍历所有子元素,使用相交检测判断位置
1029
+ children.forEach(childId => {
1030
+ const childBox = this._pageDataUtils.getViewBoxById(childId);
1031
+ if (childBox && frameBox.intersectsBox(childBox)) {
1032
+ childrenInFrame.push(childId);
1033
+ }
1034
+ else {
1035
+ childrenOutOfFrame.push(childId);
1036
+ }
1037
+ });
1038
+ // 关键判断:没有任何子元素在画板内 → 组不进入画板
1039
+ if (childrenInFrame.length === 0) {
1040
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1041
+ console.log(`Group ${item.id} has no children in frame ${targetFrameId}, skipping frame entry.`);
1042
+ return undefined;
1043
+ }
1044
+ // 部分子元素在画板外 → 延迟处理(记录到 groupsNeedProcess)
1045
+ if (childrenOutOfFrame.length > 0) {
1046
+ console.log(`Group ${item.id}: ${childrenOutOfFrame.length} children out of frame, will process at drag end.`);
1047
+ this.groupsNeedProcess.set(item.id, {
1048
+ groupId: item.id,
1049
+ frameId: targetFrameId,
1050
+ childrenOutOfFrame: childrenOutOfFrame
1051
+ });
1052
+ }
1053
+ else {
1054
+ // 所有子元素都在画板内 → 清除之前的记录
1055
+ this.groupsNeedProcess.delete(item.id);
1056
+ }
1057
+ }
1058
+ }
1059
+ else if (item.parentId) {
1060
+ const parentItem = this._pageDataUtils.getSDKItem(item.parentId);
1061
+ if (parentItem && isGroupItem(parentItem)) {
1062
+ this._pageDataUtils.deleteItemParent(item.id, item.parentId);
1063
+ }
1064
+ }
1065
+ this._pageDataUtils.moveItemsToFrame(toAddedItemIds, targetFrameId);
1066
+ }
1067
+ return targetFrameId;
1068
+ }
1069
+ // ====== 自动布局拖拽重排序功能 ======
1070
+ /**
1071
+ * @description 更新自动布局指示器
1072
+ * 基于鼠标位置实时更新被拖拽元素的 layoutInfos(row 和 column)
1073
+ * 注意:移动过程中只更新被拖拽元素的布局信息,其他元素保持不动
1074
+ * 等到交互结束时再执行整体排布
1075
+ */
1076
+ updateAutoLayoutIndicator(item) {
1077
+ const parentId = item.parentId;
1078
+ if (!parentId) {
1079
+ this.autoLayoutIndicator = null;
1080
+ this.isAutoLayoutDragging = false;
1081
+ return;
1082
+ }
1083
+ const parent = this._pageDataUtils.getSDKItem(parentId);
1084
+ if (!isFrameItem(parent) || parent.layoutMode !== FrameLayoutMode.AUTO) {
1085
+ this.autoLayoutIndicator = null;
1086
+ this.isAutoLayoutDragging = false;
1087
+ return;
1088
+ }
1089
+ this.isAutoLayoutDragging = true;
1090
+ // 记录被拖拽元素的原始行列位置(如果还没记录)
1091
+ if (this.autoLayoutDragOriginalPosition === null) {
1092
+ const originalLayoutInfo = parent.property.layoutInfos?.[item.id];
1093
+ this.autoLayoutDragOriginalPosition = {
1094
+ row: originalLayoutInfo?.row ?? 0,
1095
+ column: originalLayoutInfo?.column ?? 0,
1096
+ };
1097
+ }
1098
+ // 获取画板中所有子元素(包含被拖拽的元素,用于计算正确的 insertIndex)
1099
+ const allChildren = parent.children
1100
+ .map(id => this._pageDataUtils.getSDKItem(id))
1101
+ .filter((child) => child !== undefined);
1102
+ // 计算基于位置的布局信息(row 和 column)
1103
+ const layoutPosition = calculateAutoLayoutPositionByMouse(this.cursorPoint, allChildren, (id) => this._pageDataUtils.getViewBoxById(id), (child) => {
1104
+ const parentItem = this._pageDataUtils.getSDKItem(child.parentId ?? '');
1105
+ return isFrameItem(parentItem) ? parentItem.property.layoutInfos?.[child.id] : undefined;
1106
+ }, item.id);
1107
+ if (layoutPosition) {
1108
+ // 获取被拖拽元素的原始行号(用于计算指示器)
1109
+ const originalLayoutInfo = parent.property.layoutInfos?.[item.id];
1110
+ const originalRow = originalLayoutInfo?.row ?? 0;
1111
+ // 保存目标行列位置(延迟到 endAction 中执行实际修改)
1112
+ this.pendingLayoutPosition = { ...layoutPosition };
1113
+ // 计算指示器显示(基于鼠标位置直接计算)
1114
+ const insertPosition = calculateInsertPositionFromLayout(this.cursorPoint, allChildren, item.id, parent, originalRow, (id) => this._pageDataUtils.getViewBoxById(id), () => {
1115
+ const selectedItems = this._pageDataUtils.getSelectedItems();
1116
+ const box = new Box2();
1117
+ selectedItems.forEach(selectedItem => {
1118
+ const targeBox = this._pageDataUtils.getViewBoxById(selectedItem.id);
1119
+ box.union(targeBox);
1120
+ });
1121
+ return box;
1122
+ });
1123
+ if (insertPosition.siblingId) {
1124
+ const indicatorLine = calculateAutoLayoutIndicatorLine(insertPosition, (id) => this._pageDataUtils.getViewBoxById(id));
1125
+ this.autoLayoutIndicator = {
1126
+ frameId: parent.id,
1127
+ insertIndex: insertPosition.index,
1128
+ insertDirection: insertPosition.direction,
1129
+ targetSiblingId: insertPosition.siblingId,
1130
+ line: indicatorLine,
1131
+ targetRow: layoutPosition.row,
1132
+ targetColumn: layoutPosition.column,
1133
+ };
1134
+ }
1135
+ else {
1136
+ // 没有找到参考元素,显示在末尾
1137
+ this.autoLayoutIndicator = {
1138
+ frameId: parent.id,
1139
+ insertIndex: allChildren.length - 1,
1140
+ insertDirection: 'after',
1141
+ targetSiblingId: undefined,
1142
+ line: new Line2(), // 空线
1143
+ targetRow: layoutPosition.row,
1144
+ targetColumn: layoutPosition.column,
1145
+ };
1146
+ }
1147
+ }
1148
+ else {
1149
+ this.autoLayoutIndicator = null;
1150
+ }
1151
+ }
1152
+ /**
1153
+ * @description 执行自动布局重排序
1154
+ * 基于更新后的 layoutInfos 重新排布所有元素
1155
+ * @param draggedItem 被拖拽的元素
1156
+ */
1157
+ executeAutoLayoutReorder(draggedItem) {
1158
+ if (!this.isAutoLayoutDragging) {
1159
+ return;
1160
+ }
1161
+ // 获取拖拽的画板ID
1162
+ const frameId = this.autoLayoutIndicator?.frameId;
1163
+ if (!frameId) {
1164
+ this.autoLayoutIndicator = null;
1165
+ this.isAutoLayoutDragging = false;
1166
+ this.autoLayoutDragOriginalPosition = null;
1167
+ this.pendingLayoutPosition = null;
1168
+ return;
1169
+ }
1170
+ const frameItem = this._pageDataUtils.getSDKItem(frameId);
1171
+ if (!isFrameItem(frameItem)) {
1172
+ this.autoLayoutIndicator = null;
1173
+ this.isAutoLayoutDragging = false;
1174
+ this.autoLayoutDragOriginalPosition = null;
1175
+ this.pendingLayoutPosition = null;
1176
+ return;
1177
+ }
1178
+ // 执行行列修改(延迟执行)
1179
+ if (draggedItem && this.autoLayoutDragOriginalPosition && this.pendingLayoutPosition) {
1180
+ // 调整其他元素的行列信息
1181
+ adjustOtherElementsLayoutInfo(frameItem, draggedItem.id, this.autoLayoutDragOriginalPosition.row, this.autoLayoutDragOriginalPosition.column, this.pendingLayoutPosition.row, this.pendingLayoutPosition.column, (id) => this._pageDataUtils.getSDKItem(id));
1182
+ // 更新被拖拽元素的 layoutInfo
1183
+ updateItemLayoutInfo(draggedItem, frameItem, this.pendingLayoutPosition);
1184
+ }
1185
+ // 基于更新后的 layoutInfos 重新排布所有元素
1186
+ this._pageDataUtils.applyFrameAutoLayout(frameId);
1187
+ // 清理状态
1188
+ this.autoLayoutIndicator = null;
1189
+ this.isAutoLayoutDragging = false;
1190
+ this.autoLayoutDragOriginalPosition = null;
1191
+ this.pendingLayoutPosition = null;
1192
+ }
1193
+ refreshFrameChildren() {
1194
+ const selectedItems = this._pageDataUtils.getSelectedItems();
1195
+ if (selectedItems.length === 1) {
1196
+ this._pageDataUtils.refreshFrameChildren(selectedItems[0].id);
1197
+ }
1198
+ }
1199
+ }