@gedit/editor-2d 0.3.14 → 0.3.17

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 (39) hide show
  1. package/lib/browser/editor2d-contribution.d.ts.map +1 -1
  2. package/lib/browser/editor2d-contribution.js +19 -18
  3. package/lib/browser/editor2d-contribution.js.map +1 -1
  4. package/lib/browser/playground/canvas-layer.d.ts +2 -1
  5. package/lib/browser/playground/canvas-layer.d.ts.map +1 -1
  6. package/lib/browser/playground/canvas-layer.js +8 -1
  7. package/lib/browser/playground/canvas-layer.js.map +1 -1
  8. package/lib/browser/playground/path-edit/path-edit-layer-move-point.d.ts.map +1 -1
  9. package/lib/browser/playground/path-edit/path-edit-layer-move-point.js +38 -4
  10. package/lib/browser/playground/path-edit/path-edit-layer-move-point.js.map +1 -1
  11. package/lib/browser/playground/path-edit/path-edit-layer-svg-path.d.ts +9 -2
  12. package/lib/browser/playground/path-edit/path-edit-layer-svg-path.d.ts.map +1 -1
  13. package/lib/browser/playground/path-edit/path-edit-layer-svg-path.js +232 -13
  14. package/lib/browser/playground/path-edit/path-edit-layer-svg-path.js.map +1 -1
  15. package/lib/browser/playground/path-edit/utils.d.ts +16 -3
  16. package/lib/browser/playground/path-edit/utils.d.ts.map +1 -1
  17. package/lib/browser/playground/path-edit/utils.js +63 -13
  18. package/lib/browser/playground/path-edit/utils.js.map +1 -1
  19. package/lib/browser/playground/path-edit-layer.d.ts +46 -3
  20. package/lib/browser/playground/path-edit-layer.d.ts.map +1 -1
  21. package/lib/browser/playground/path-edit-layer.js +533 -171
  22. package/lib/browser/playground/path-edit-layer.js.map +1 -1
  23. package/lib/browser/playground/playground-contribution.d.ts.map +1 -1
  24. package/lib/browser/playground/playground-contribution.js +2 -0
  25. package/lib/browser/playground/playground-contribution.js.map +1 -1
  26. package/lib/browser/utils/bezier.path.utils.d.ts.map +1 -1
  27. package/lib/browser/utils/bezier.path.utils.js +3 -0
  28. package/lib/browser/utils/bezier.path.utils.js.map +1 -1
  29. package/package.json +7 -7
  30. package/src/browser/editor2d-contribution.ts +19 -18
  31. package/src/browser/playground/canvas-layer.ts +6 -1
  32. package/src/browser/playground/path-edit/path-edit-layer-move-point.tsx +50 -5
  33. package/src/browser/playground/path-edit/path-edit-layer-svg-path.tsx +303 -26
  34. package/src/browser/playground/path-edit/utils.tsx +82 -17
  35. package/src/browser/playground/path-edit-layer.tsx +585 -216
  36. package/src/browser/playground/playground-contribution.ts +2 -0
  37. package/src/browser/style/path-edit-layer.less +17 -5
  38. package/src/browser/svg/drag_path.svg +17 -0
  39. package/src/browser/utils/bezier.path.utils.ts +3 -0
@@ -17,6 +17,7 @@ import type { Editor2dDocument, Editor2dPathNode } from '../model';
17
17
  import { PlaygroundContext2d } from './playground-context';
18
18
  import { SelectableTreeNode, TreeSelection } from '@gedit/tree';
19
19
  import {
20
+ asVec,
20
21
  PathChild,
21
22
  PathSchema,
22
23
  PATH_FUNC_TYPE,
@@ -37,8 +38,13 @@ import {
37
38
  updatePathNodeData,
38
39
  inverseTransformToData,
39
40
  transformToData,
41
+ shiftAngleArray,
40
42
  } from './path-edit';
41
43
 
44
+ import { Command } from '@gedit/command';
45
+ import { isOSX } from '@gedit/application-common';
46
+ import { getParallelPositionByPoint } from '../utils/bezier.path.utils';
47
+
42
48
  export interface PathEditLayerEventContextProps {
43
49
  onPointMouseDown: (e: React.MouseEvent, p: PathChild) => void;
44
50
  onClosePath: () => void;
@@ -53,11 +59,52 @@ export interface PathEditLayerEventContextProps {
53
59
  key: string
54
60
  ) => void;
55
61
  onPathAddPoint: (index: number, e: React.MouseEvent) => void;
62
+ isShiftKey?: boolean;
63
+ isCtrlKey?: boolean;
56
64
  }
57
65
  export const PathEditLayerEventContext = React.createContext<PathEditLayerEventContextProps>(
58
66
  {} as PathEditLayerEventContextProps
59
67
  );
60
68
 
69
+ export namespace Editor2dPathKeyCommands {
70
+ const EDITOR2D_PATH_CATEGORY = 'Editor2dPath';
71
+ export const TAB_PATH: Command = {
72
+ id: 'editor2d.path.tab',
73
+ category: EDITOR2D_PATH_CATEGORY,
74
+ label: '下一个点',
75
+ };
76
+ // export const SHOW_ALL_BEZIER: Command = {
77
+ // id: 'editor2d.path.showAllBezier',
78
+ // category: EDITOR2D_PATH_CATEGORY,
79
+ // label: '显示全部贝塞尔点',
80
+ // };
81
+ export const SELECT_All: Command = {
82
+ id: 'editor2d.path.selectAll',
83
+ category: EDITOR2D_PATH_CATEGORY,
84
+ label: '选择全部',
85
+ };
86
+ export const BEZIER_STRAIGHT: Command = {
87
+ id: 'editor2d.path.bezierStraight',
88
+ category: EDITOR2D_PATH_CATEGORY,
89
+ label: '无贝塞尔',
90
+ };
91
+ export const BEZIER_EQUAL: Command = {
92
+ id: 'editor2d.path.bezierEqual',
93
+ category: EDITOR2D_PATH_CATEGORY,
94
+ label: '左右相等',
95
+ };
96
+ export const BEZIER_BREAK: Command = {
97
+ id: 'editor2d.path.bezierBreak',
98
+ category: EDITOR2D_PATH_CATEGORY,
99
+ label: '左右断开',
100
+ };
101
+ export const BEZIER_UNEQUAL: Command = {
102
+ id: 'editor2d.path.bezierUnequal',
103
+ category: EDITOR2D_PATH_CATEGORY,
104
+ label: '左右不相等',
105
+ };
106
+ }
107
+
61
108
  /**
62
109
  * 动态绘制层
63
110
  */
@@ -75,6 +122,34 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
75
122
  currentPathNode?: Editor2dPathNode;
76
123
  startPos?: PathChild;
77
124
 
125
+ // 显示全部贝塞尔点
126
+ protected altKey = false;
127
+
128
+ // 按住 shit 键
129
+ protected shiftKey = false;
130
+
131
+ // 按住 ctrl 或 ⌘ 键;
132
+ protected ctrlKey = false;
133
+
134
+ // 历史记录
135
+ // protected history:
136
+
137
+ protected historyUri?: string;
138
+
139
+ protected _historyIndex = 0;
140
+
141
+ protected noHistory = false;
142
+
143
+ get historyIndex(): number {
144
+ const h = this._historyIndex;
145
+ this._historyIndex++;
146
+ return h;
147
+ }
148
+
149
+ set historyIndex(h: number) {
150
+ this._historyIndex = h;
151
+ }
152
+
78
153
  // 访录当前图层在 timeline 里的监听事件
79
154
  layerDisposeMap = new Map<string, Disposable>();
80
155
  protected bezierStartPos?: {
@@ -105,13 +180,14 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
105
180
  }
106
181
 
107
182
  reNodeData(): void {
183
+ this.clearHistory();
108
184
  this.currentPathNode = undefined;
109
185
  this.startPos = undefined;
110
186
  this.pathPointSelection!.clearSelection();
111
187
  this.pathPointSelection!.selectMode = PathSelectMode.ADD_PATH;
112
188
  this.docDispose?.dispose();
113
189
  }
114
- updateNodeData(): void {
190
+ updateNodeData(noHistory?: boolean): void {
115
191
  if (!this.currentPathNode) {
116
192
  return;
117
193
  }
@@ -119,7 +195,69 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
119
195
  // console.log('updateNodeData', nodeData);
120
196
  // 直接更新数据,,动画 updateAnimationNode 里会把数据删了;
121
197
  this.currentPathNode.path = nodeData.path;
198
+ this.currentPathNode.selected = true;
122
199
  this.document?.updateNode(this.currentPathNode, nodeData, true);
200
+ this.addHistoryData(noHistory);
201
+ }
202
+ reloadNodeData(content: string): void {
203
+ if (!this.currentPathNode || !this.pathPointSelection) {
204
+ return;
205
+ }
206
+ const path = JSON.parse(content) as PathSchema;
207
+ this.currentPathNode.path = path;
208
+ this.document?.updateNode(this.currentPathNode, { path }, true);
209
+ this.pathPointSelection.selection = path.paths[path.paths.length - 1]
210
+ ? [{ pointId: path.paths[path.paths.length - 1].id }]
211
+ : [];
212
+ if (!path.paths.length) {
213
+ this.pathPointSelection.enterPathMode([]);
214
+ }
215
+ this.draw();
216
+ }
217
+ clearHistory(): void {
218
+ if (this.historyUri) {
219
+ this.context.historyService.clearHistory(this.historyUri);
220
+ this.historyUri = undefined;
221
+ this.context.historyService.switchHistory(this.context.document.uri);
222
+ }
223
+ this.historyIndex = 0;
224
+ }
225
+ addHistoryData(noHistory?: boolean): void {
226
+ const nodePath = this.currentPathNode?.path;
227
+ if (!nodePath || noHistory || !this.historyUri) {
228
+ return;
229
+ }
230
+
231
+ const index = this.historyIndex;
232
+ this.context.historyService.add(this.historyUri, {
233
+ key: index,
234
+ data: {
235
+ content: JSON.stringify(nodePath),
236
+ resource: {
237
+ reload: (content: string) => {
238
+ this.reloadNodeData(content);
239
+ },
240
+ resource: {
241
+ uri: this.historyUri,
242
+ },
243
+ },
244
+ },
245
+ });
246
+ }
247
+ createHistory(): void {
248
+ if (!this.currentPathNode) {
249
+ return;
250
+ }
251
+ this.historyUri = `${this.context.document.uri}?${this.currentPathNode?.id}`;
252
+ if (this.context.historyService.currentUri !== this.historyUri) {
253
+ this.context.historyService.switchHistory(this.historyUri);
254
+ if (
255
+ !this.context.historyService.getHistoryNavigator(this.historyUri)
256
+ ?.length
257
+ ) {
258
+ this.addHistoryData();
259
+ }
260
+ }
123
261
  }
124
262
  // updateNodePathData(): void {
125
263
  // if (!this.currentPathNode) {
@@ -237,14 +375,199 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
237
375
  }
238
376
  }
239
377
 
378
+ protected updateCurrentPointBezierType(type: PATH_FUNC_TYPE): void {
379
+ if (!this.currentPathNode || !this.pathPointSelection) {
380
+ return;
381
+ }
382
+ const { selection = [] } = this.pathPointSelection;
383
+ selection.forEach(item => {
384
+ const path = this.currentPathNode!.path;
385
+ const point = path.paths.find(c => c.id === item.pointId);
386
+ if (point) {
387
+ point.type = type;
388
+ switch (type) {
389
+ case PATH_FUNC_TYPE.STRAIGHT:
390
+ delete point.x1;
391
+ delete point.y1;
392
+ delete point.x2;
393
+ delete point.y2;
394
+ break;
395
+ case PATH_FUNC_TYPE.EQUAL:
396
+ case PATH_FUNC_TYPE.UNEQUAL:
397
+ case PATH_FUNC_TYPE.BREAK: {
398
+ const inP1 = 'x1' in point && 'y1' in point;
399
+ const inP2 = 'x2' in point && 'y2' in point;
400
+ if (type === PATH_FUNC_TYPE.BREAK && inP1 && inP2) {
401
+ break;
402
+ }
403
+ const index = path.paths.findIndex(c => c.id === point.id);
404
+ const prevPoint =
405
+ path.paths[index - 1] || path.paths[path.paths.length - 1];
406
+ const nextPoint = path.paths[index + 1] || path.paths[0];
407
+ // 计算平衡线;
408
+ // 点线长度计算两个点的长的距一半;
409
+ const { p1, p2 /* isReverse */ } = getParallelPositionByPoint(
410
+ prevPoint,
411
+ nextPoint,
412
+ point
413
+ );
414
+ if (type === PATH_FUNC_TYPE.BREAK) {
415
+ if (inP1 && !inP2) {
416
+ point.x2 = p2.x;
417
+ point.y2 = p2.y;
418
+ } else if (inP2 && !inP1) {
419
+ point.x1 = p1.x;
420
+ point.y1 = p1.y;
421
+ }
422
+ }
423
+ point.x1 = p1.x;
424
+ point.y1 = p1.y;
425
+ point.x2 = p2.x;
426
+ point.y2 = p2.y;
427
+ break;
428
+ }
429
+
430
+ default:
431
+ break;
432
+ }
433
+ }
434
+ });
435
+ this.updateNodeData();
436
+ }
437
+
438
+ protected registerKeybindings(): Disposable[] {
439
+ const ctrlKey = !isOSX ? 'control' : 'meta';
440
+ return [
441
+ // alt shift 之类在 keybindings 里面不能单个做快捷键
442
+ this.listenGlobalEvent('keydown', e => {
443
+ // alt 显示全部贝赛尔点
444
+ if (this.isEnabled && e.key.toLowerCase() === 'alt') {
445
+ this.altKey = true;
446
+ this.draw();
447
+ }
448
+ if (this.isEnabled && e.key.toLowerCase() === 'shift') {
449
+ this.shiftKey = true;
450
+ this.draw();
451
+ }
452
+
453
+ if (this.isEnabled && e.key.toLowerCase() === ctrlKey) {
454
+ this.ctrlKey = true;
455
+ this.draw();
456
+ }
457
+ }),
458
+ this.listenGlobalEvent('keyup', (e: KeyboardEvent) => {
459
+ if (this.isEnabled && e.key.toLowerCase() === 'alt') {
460
+ this.altKey = false;
461
+ this.draw();
462
+ }
463
+ if (this.isEnabled && e.key.toLowerCase() === 'shift') {
464
+ this.shiftKey = false;
465
+ this.draw();
466
+ }
467
+ if (this.isEnabled && e.key.toLowerCase() === ctrlKey) {
468
+ this.ctrlKey = false;
469
+ this.draw();
470
+ }
471
+ }),
472
+ // tab 切换点
473
+ this.commands.registerCommand(Editor2dPathKeyCommands.TAB_PATH, {
474
+ execute: () => {
475
+ const currentPoint = this.pathPointSelection.selection?.[0]?.pointId;
476
+ const { paths = [] } = this.currentPathNode?.path || {};
477
+ const index = currentPoint
478
+ ? paths.findIndex(c => c.id === currentPoint)
479
+ : -1;
480
+ let nextPoint;
481
+ if (index === -1) {
482
+ nextPoint = paths[0];
483
+ } else {
484
+ nextPoint = paths[index + 1] || paths[0];
485
+ }
486
+ this.selectionChange(nextPoint);
487
+ },
488
+ isEnabled: () => !!this.isEnabled,
489
+ }),
490
+ this.keybindings.registerKeybindings({
491
+ command: Editor2dPathKeyCommands.TAB_PATH.id,
492
+ keybinding: 'tab',
493
+ when: 'editor2dWidgetFocus',
494
+ }),
495
+ // all 全选
496
+ this.commands.registerCommand(Editor2dPathKeyCommands.SELECT_All, {
497
+ execute: () => {
498
+ if (!this.currentPathNode || !this.pathPointSelection) {
499
+ return;
500
+ }
501
+ const { paths = [] } = this.currentPathNode.path;
502
+ const selection = paths.map(c => ({
503
+ pointId: c.id,
504
+ }));
505
+ this.pathPointSelection.enterSelectBezierMode(selection);
506
+ },
507
+ isEnabled: () => !!this.isEnabled,
508
+ }),
509
+ this.keybindings.registerKeybindings({
510
+ command: Editor2dPathKeyCommands.SELECT_All.id,
511
+ keybinding: isOSX ? 'cmd+a' : 'ctrl+a',
512
+ when: 'editor2dWidgetFocus',
513
+ }),
514
+ // 贝赛尔点
515
+ this.commands.registerCommand(Editor2dPathKeyCommands.BEZIER_STRAIGHT, {
516
+ execute: () => {
517
+ this.updateCurrentPointBezierType(PATH_FUNC_TYPE.STRAIGHT);
518
+ },
519
+ isEnabled: () => !!this.isEnabled,
520
+ }),
521
+ this.keybindings.registerKeybindings({
522
+ command: Editor2dPathKeyCommands.BEZIER_STRAIGHT.id,
523
+ keybinding: '1',
524
+ when: 'editor2dWidgetFocus',
525
+ }),
526
+ this.commands.registerCommand(Editor2dPathKeyCommands.BEZIER_EQUAL, {
527
+ execute: () => {
528
+ this.updateCurrentPointBezierType(PATH_FUNC_TYPE.EQUAL);
529
+ },
530
+ isEnabled: () => !!this.isEnabled,
531
+ }),
532
+ this.keybindings.registerKeybindings({
533
+ command: Editor2dPathKeyCommands.BEZIER_EQUAL.id,
534
+ keybinding: '2',
535
+ when: 'editor2dWidgetFocus',
536
+ }),
537
+ this.commands.registerCommand(Editor2dPathKeyCommands.BEZIER_BREAK, {
538
+ execute: () => {
539
+ this.updateCurrentPointBezierType(PATH_FUNC_TYPE.BREAK);
540
+ },
541
+ isEnabled: () => !!this.isEnabled,
542
+ }),
543
+ this.keybindings.registerKeybindings({
544
+ command: Editor2dPathKeyCommands.BEZIER_BREAK.id,
545
+ keybinding: '3',
546
+ when: 'editor2dWidgetFocus',
547
+ }),
548
+ this.commands.registerCommand(Editor2dPathKeyCommands.BEZIER_UNEQUAL, {
549
+ execute: () => {
550
+ this.updateCurrentPointBezierType(PATH_FUNC_TYPE.UNEQUAL);
551
+ },
552
+ isEnabled: () => !!this.isEnabled,
553
+ }),
554
+ this.keybindings.registerKeybindings({
555
+ command: Editor2dPathKeyCommands.BEZIER_UNEQUAL.id,
556
+ keybinding: '4',
557
+ when: 'editor2dWidgetFocus',
558
+ }),
559
+ ];
560
+ }
561
+
240
562
  onReady(): void {
241
563
  this.addDispose([
242
- this.context.historyService.onHistoryBack(e => {
243
- this.reNodeData();
244
- this.pathPointSelection!.selectMode = PathSelectMode.ADD_PATH;
245
- this.pathPointSelection!.clearSelection();
246
- this.editorState.toDefaultState();
247
- }),
564
+ ...this.registerKeybindings(),
565
+ // this.context.historyService.onHistoryBack(e => {
566
+ // this.reNodeData();
567
+ // this.pathPointSelection!.selectMode = PathSelectMode.ADD_PATH;
568
+ // this.pathPointSelection!.clearSelection();
569
+ // this.editorState.toDefaultState();
570
+ // }),
248
571
  this.editorState.onStateChange(e => {
249
572
  /**
250
573
  * 1. 不是路径编辑状态时,清空数据
@@ -262,7 +585,6 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
262
585
  return;
263
586
  }
264
587
  const selectNodes = this.document?.getSelectedNodes() || [];
265
-
266
588
  const pathSelectsNodes = selectNodes.filter(
267
589
  c => c.displayType === GameObjectBaseType.PATH
268
590
  );
@@ -272,6 +594,7 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
272
594
  this.currentPathNode = deepClone(
273
595
  pathSelectsNodes[0]
274
596
  ) as Editor2dPathNode;
597
+ this.createHistory();
275
598
  if (
276
599
  this.currentPathNode.useAnimationId &&
277
600
  this.document?.timelineService?.currentTimelineData &&
@@ -282,13 +605,17 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
282
605
  this.onRegisterTimelineEvent();
283
606
  }
284
607
 
285
- const selectionNode = [
286
- {
287
- pointId: this.currentPathNode.path.paths[
288
- this.currentPathNode.path.paths.length - 1
289
- ].id,
290
- },
291
- ];
608
+ const selectionNode = this.currentPathNode.path.paths[
609
+ this.currentPathNode.path.paths.length - 1
610
+ ]?.id
611
+ ? [
612
+ {
613
+ pointId: this.currentPathNode.path.paths[
614
+ this.currentPathNode.path.paths.length - 1
615
+ ].id,
616
+ },
617
+ ]
618
+ : [];
292
619
  this.currentPathNode.path.closed
293
620
  ? this.pathPointSelection.enterSelectBezierMode(selectionNode)
294
621
  : this.pathPointSelection.enterPathMode(selectionNode);
@@ -360,28 +687,14 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
360
687
  );
361
688
  const { paths = [], closed } = this.currentPathNode?.path || {};
362
689
  const pathSelects: PathPointSelection[] = [];
363
- const {
364
- position = { x: 0, y: 1 },
365
- scale = { x: 1, y: 1 },
366
- rotation = 0,
367
- } = this.currentPathNode!;
368
- const offset = this.getBoundsOffset();
369
- const center = {
370
- x: -offset.x,
371
- y: -offset.y,
372
- };
373
690
  paths.forEach(p => {
374
- const { x: $x, y: $y } = p;
375
- let x = $x;
376
- let y = $y;
377
- const r = transformToData({ x, y }, { scale, rotation }, center);
378
- x = r.x + position.x;
379
- y = r.y + position.y;
380
- const wh = (4 / this.config.finalScale) * 2;
691
+ const { x, y } = this.pathPosToCanvasPos(p);
692
+ // 半径
693
+ const wh = 4 * this.config.finalScale;
381
694
  if (
382
- x >= selectBound.x && // 左
695
+ x - wh >= selectBound.x && // 左
383
696
  x + wh <= selectBound.x + selectBound.width && // 右
384
- y >= selectBound.y && // 上
697
+ y - wh >= selectBound.y && // 上
385
698
  y + wh <= selectBound.y + selectBound.height // 下
386
699
  ) {
387
700
  pathSelects.push({
@@ -413,102 +726,110 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
413
726
  }
414
727
  this.reNodeData();
415
728
  }), */
729
+ // 拦掉画布点击事件,失焦再次点击时会触发 widget 的点击事件, 会聚焦到 widget 上
730
+ this.listenPlaygroundEvent('click', (event: MouseEvent) => {
731
+ if (this.isEnabled) {
732
+ event.preventDefault();
733
+ event.stopPropagation();
734
+ this.createHistory();
735
+ }
736
+ }),
416
737
  // 画布新增点点击事件;
417
738
  this.listenPlaygroundEvent('mousedown', (event: MouseEvent) => {
418
739
  // 只在左键点击时绘制
419
- if (event.buttons !== 1) {
740
+ if (event.buttons !== 1 || !this.isEnabled) {
420
741
  return;
421
742
  }
743
+ event.preventDefault();
744
+ event.stopPropagation();
422
745
  this.startDrag(event.clientX, event.clientY, {
423
746
  onDragStart: e => {
424
- // 当前编辑模式为路径编辑
425
- if (this.editorState.is(EditorState.EDIT_PATH_STATE.id)) {
426
- const { selectMode } = this.pathPointSelection || {};
427
- /**
428
- * 拦截画布点击事件,不处理绘制;
429
- * 1. 闭合路径时;
430
- * 2. 带区域选择模式时;
431
- * 3. 多点选择时;
432
- */
433
- if (
434
- // 判断当前 select 是否 path 路径
435
- (this.currentPathNode && this.currentPathNode.path.closed) ||
436
- selectMode !== PathSelectMode.ADD_PATH ||
437
- (this.pathPointSelection?.selection &&
438
- this.pathPointSelection.selection.length > 1)
439
- ) {
440
- return;
747
+ const { selectMode, selection } = this.pathPointSelection || {};
748
+ /**
749
+ * 拦截画布点击事件,不处理绘制;
750
+ * 1. 闭合路径时;
751
+ * 2. 带区域选择模式时;
752
+ * 3. 多点选择时;
753
+ */
754
+ if (
755
+ // 判断当前 select 是否 path 路径
756
+ (this.currentPathNode && this.currentPathNode.path.closed) ||
757
+ selectMode !== PathSelectMode.ADD_PATH ||
758
+ (selection && selection.length > 1)
759
+ ) {
760
+ return;
761
+ }
762
+ let { x, y } = this.canvasPosToPathPos({
763
+ x: e.endPos.x / (this.config?.finalScale || 1),
764
+ y: e.endPos.y / (this.config?.finalScale || 1),
765
+ });
766
+
767
+ if (selection) {
768
+ const selectionPoint = selection[0];
769
+ const index =
770
+ this.currentPathNode?.path.paths.findIndex(
771
+ c => c.id === selectionPoint.pointId
772
+ ) ?? -1;
773
+ const node = this.currentPathNode?.path.paths[index];
774
+ if (node) {
775
+ // 按住 ctrl 加点时;
776
+ if (this.ctrlKey && node.type !== PATH_FUNC_TYPE.STRAIGHT) {
777
+ const isReverse = index === 0;
778
+ if (isReverse) {
779
+ node.x1 = node.x;
780
+ node.y1 = node.y;
781
+ } else {
782
+ node.x2 = node.x;
783
+ node.y2 = node.y;
784
+ }
785
+ }
786
+ // 按住 shift 时,生成固定角度的点;
787
+ if (this.shiftKey) {
788
+ const arc = asVec(node, { x, y });
789
+ let angle = (arc.ang / Math.PI) * 180;
790
+ angle = angle < 0 ? 360 + angle : angle;
791
+ const angleIndex = shiftAngleArray.findIndex(
792
+ a => a > angle
793
+ );
794
+ const prevAngle =
795
+ shiftAngleArray[angleIndex - 1] ??
796
+ shiftAngleArray[shiftAngleArray.length - 1];
797
+ const nextAngle = shiftAngleArray[angleIndex];
798
+ const convertAngle =
799
+ Math.abs(angle - prevAngle) > Math.abs(nextAngle - angle)
800
+ ? nextAngle
801
+ : prevAngle;
802
+ // 取转换角度后的坐标值
803
+ const rad = (convertAngle / 180) * Math.PI;
804
+ x = Math.cos(rad) * arc.len + node.x;
805
+ y = Math.sin(rad) * arc.len + node.y;
806
+ }
441
807
  }
442
- const {
443
- position = { x: 0, y: 0 },
444
- scale = { x: 1, y: 1 },
445
- rotation = 0,
446
- } = this.currentPathNode || {};
447
- const offset = this.getBoundsOffset();
448
- let x =
449
- e.endPos.x / (this.config?.finalScale || 1) -
450
- position.x -
451
- offset.x;
452
- let y =
453
- e.endPos.y / (this.config?.finalScale || 1) -
454
- position.y -
455
- offset.y;
456
- const center = {
457
- x: -offset.x,
458
- y: -offset.y,
459
- };
460
- const { x: rx, y: ry } = inverseTransformToData(
461
- { x, y },
462
- { scale, rotation },
463
- center
464
- );
465
- x = rx;
466
- y = ry;
467
- this.startPos = {
468
- x,
469
- y,
470
- };
471
- this.startPos.id = generateUuid();
472
- this.startPos.type = PATH_FUNC_TYPE.STRAIGHT;
473
- this.addPath(this.startPos);
474
- this.pathPointSelection!.enterPathMode([
475
- {
476
- pointId: this.startPos.id,
477
- },
478
- ]);
479
808
  }
809
+
810
+ this.startPos = {
811
+ x,
812
+ y,
813
+ };
814
+ this.startPos.id = generateUuid();
815
+ this.startPos.type = PATH_FUNC_TYPE.STRAIGHT;
816
+ this.addPath(this.startPos);
817
+ this.pathPointSelection!.enterPathMode([
818
+ {
819
+ pointId: this.startPos.id,
820
+ },
821
+ ]);
822
+ this.updateNodeData(true);
480
823
  },
481
824
  onDrag: e => {
482
825
  if (!this.startPos) {
483
826
  return;
484
827
  }
485
828
  // const pos = this.getPosFromMouseEvent(e);
486
- const {
487
- path,
488
- position = { x: 0, y: 0 },
489
- scale = { x: 1, y: 1 },
490
- rotation = 0,
491
- } = this.currentPathNode || {};
492
- const offset = this.getBoundsOffset();
493
- let x =
494
- e.endPos.x / (this.config?.finalScale || 1) -
495
- position.x -
496
- offset.x;
497
- let y =
498
- e.endPos.y / (this.config?.finalScale || 1) -
499
- position.y -
500
- offset.y;
501
- const center = {
502
- x: -offset.x,
503
- y: -offset.y,
504
- };
505
- const { x: rx, y: ry } = inverseTransformToData(
506
- { x, y },
507
- { scale, rotation },
508
- center
509
- );
510
- x = rx;
511
- y = ry;
829
+ const { path } = this.currentPathNode || {};
830
+ const eX = e.endPos.x / (this.config?.finalScale || 1);
831
+ const eY = e.endPos.y / (this.config?.finalScale || 1);
832
+ let { x, y } = this.canvasPosToPathPos({ x: eX, y: eY });
512
833
 
513
834
  const pathNode = path?.paths.find(
514
835
  c => c.id === this.startPos?.id
@@ -521,24 +842,43 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
521
842
  Math.abs(y - this.startPos.y)
522
843
  ) >= 5
523
844
  ) {
524
- const x1 = pathNode.x * 2 - x;
525
- const y1 = pathNode.y * 2 - y;
845
+ let x1 = pathNode.x * 2 - x;
846
+ let y1 = pathNode.y * 2 - y;
526
847
  const reverse = this.isOnePoint();
848
+ if (this.shiftKey) {
849
+ // 计算原点到当前点的角度
850
+ const a = Math.abs(x - this.startPos.x);
851
+ const b = Math.abs(y - this.startPos.y);
852
+ const max = Math.max(a, b);
853
+ if (max === a) {
854
+ y = this.startPos.y;
855
+ y1 = this.startPos.y;
856
+ } else {
857
+ x = this.startPos.x;
858
+ x1 = this.startPos.x;
859
+ }
860
+ }
527
861
  pathNode.x2 = toFixedValue(reverse ? x1 : x, 2);
528
862
  pathNode.y2 = toFixedValue(reverse ? y1 : y, 2);
529
863
  pathNode.x1 = toFixedValue(reverse ? x : x1, 2);
530
864
  pathNode.y1 = toFixedValue(reverse ? y : y1, 2);
531
865
  pathNode.type = PATH_FUNC_TYPE.EQUAL;
532
866
  // console.log(this.startPos, path?.paths);
533
- this.updateNodeData();
867
+ this.updateNodeData(true);
534
868
  }
535
869
  },
536
870
  onDragEnd: () => {
537
871
  this.startPos = undefined;
872
+ this.updateNodeData();
538
873
  },
539
874
  });
540
875
  }),
541
876
  this.listenPlaygroundEvent('dblclick', (e: MouseEvent) => {
877
+ if (!this.isEnabled) {
878
+ return;
879
+ }
880
+ e.preventDefault();
881
+ e.stopPropagation();
542
882
  // 双击画布时,关闭路径
543
883
  // 1. 在路径编辑状态时,不处理;
544
884
  // 2. 选中的是 path 路径时,不处理,新增点;
@@ -546,7 +886,7 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
546
886
  // 4. 右键时,不处理;
547
887
  // 5. 不是路径编辑状态时,不处理;
548
888
  const isExit =
549
- this.editorState.is(EditorState.EDIT_PATH_STATE.id) &&
889
+ this.isEnabled &&
550
890
  this.pathPointSelection.selectMode === PathSelectMode.SELECT_BEZIER;
551
891
  if (isExit) {
552
892
  const playgroundLayer = this.pipelineLayers.find(
@@ -574,6 +914,8 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
574
914
  !this.currentPathNode ||
575
915
  selectNodes[0].id !== this.currentPathNode.id
576
916
  ) {
917
+ // 清空上个 path 的 history
918
+ this.clearHistory();
577
919
  // 选中的不是当前的 path 路径, 切换 currentPathNode;
578
920
  this.currentPathNode = selectNodes[0] as Editor2dPathNode;
579
921
  if (
@@ -596,6 +938,7 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
596
938
  this.currentPathNode.path.closed
597
939
  ? pathPointSelection.enterSelectBezierMode(selectionNode)
598
940
  : pathPointSelection.enterPathMode(selectionNode);
941
+ this.createHistory();
599
942
  // this.updateNodePathData();
600
943
  }
601
944
  return;
@@ -663,13 +1006,18 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
663
1006
  { x: 0, y: 0 }
664
1007
  );
665
1008
  this.currentPathNode.selected = true;
1009
+ this.currentPathNode.path.paths = [];
666
1010
  this.context.selection.clearSelection();
667
- this.context.selection.addSelection({
668
- node: this.currentPathNode as Readonly<SelectableTreeNode>,
669
- type: TreeSelection.SelectionType.TOGGLE,
1011
+ setTimeout(() => {
1012
+ this.context.selection.addSelection({
1013
+ node: this.currentPathNode as Readonly<SelectableTreeNode>,
1014
+ type: TreeSelection.SelectionType.TOGGLE,
1015
+ });
670
1016
  });
671
1017
  }
672
- this.currentPathNode.path.paths = this.currentPathNode.path.paths || []; // array 会被复用,复制一个,保证不会被修改
1018
+ // 把空数组先插入历史;
1019
+ this.createHistory();
1020
+ // this.currentPathNode.path.paths = this.currentPathNode.path.paths || []; // array 会被复用,复制一个,保证不会被修改
673
1021
  if (this.isOnePoint() && this.currentPathNode.path.paths.length > 1) {
674
1022
  // 第一帧
675
1023
  this.currentPathNode.path.paths.unshift(pos);
@@ -683,16 +1031,6 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
683
1031
  // });
684
1032
  }
685
1033
 
686
- protected onBezierPointMove(pos: PointSchema): void {
687
- const { key, path: p } = this.bezierStartPos!;
688
- const { path = { paths: [] } } = this.currentPathNode || {};
689
- const pathNode = path.paths.find(c => c.id === p.id);
690
- if (!pathNode || !this.currentPathNode) {
691
- return;
692
- }
693
- setBezierMovePoint(pathNode, key, pos);
694
- this.updateNodeData();
695
- }
696
1034
  protected onBezierPointMouseDown(
697
1035
  e: React.MouseEvent,
698
1036
  p: PathChild,
@@ -704,9 +1042,10 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
704
1042
  e.preventDefault();
705
1043
  e.stopPropagation();
706
1044
  const pos = this.getPosFromMouseEvent(e);
1045
+ const { x: startX, y: startY } = this.canvasPosToPathPos(pos);
707
1046
  this.bezierStartPos = {
708
- x: pos.x,
709
- y: pos.y,
1047
+ x: startX,
1048
+ y: startY,
710
1049
  key,
711
1050
  path: p,
712
1051
  };
@@ -719,28 +1058,20 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
719
1058
  this.startDrag(e.clientX, e.clientY, {
720
1059
  onDrag: e => {
721
1060
  if (this.bezierStartPos) {
722
- const {
723
- position = { x: 0, y: 0 },
724
- scale = { x: 1, y: 1 },
725
- rotation = 0,
726
- } = this.currentPathNode || {};
727
- const offset = this.getBoundsOffset();
728
- let x =
729
- e.endPos.x / (this.config?.finalScale || 1) - position.x - offset.x;
730
- let y =
731
- e.endPos.y / (this.config?.finalScale || 1) - position.y - offset.y;
732
- const center = {
733
- x: -offset.x,
734
- y: -offset.y,
735
- };
736
- const { x: currentX, y: currentY } = inverseTransformToData(
737
- { x, y },
738
- { scale, rotation },
739
- center
740
- );
741
- x = currentX;
742
- y = currentY;
743
- this.onBezierPointMove({ x, y });
1061
+ const { path = { paths: [] } } = this.currentPathNode || {};
1062
+ const { x, y } = this.canvasPosToPathPos({
1063
+ x: e.endPos.x / (this.config?.finalScale || 1),
1064
+ y: e.endPos.y / (this.config?.finalScale || 1),
1065
+ });
1066
+ const pathNode = path.paths.find(c => c.id === p.id);
1067
+ if (!pathNode) {
1068
+ return;
1069
+ }
1070
+ setBezierMovePoint(pathNode, key, { x, y }, this.bezierStartPos, {
1071
+ shiftKey: this.shiftKey,
1072
+ ctrlKey: this.ctrlKey,
1073
+ });
1074
+ this.updateNodeData(true);
744
1075
  }
745
1076
  },
746
1077
  onDragEnd: () => {
@@ -756,6 +1087,7 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
756
1087
  ]);
757
1088
  }
758
1089
  }
1090
+ this.updateNodeData();
759
1091
  },
760
1092
  });
761
1093
  this.draw();
@@ -774,19 +1106,13 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
774
1106
  return event;
775
1107
  }
776
1108
 
777
- protected onPointMouseDown(
778
- e: React.MouseEvent,
779
- currentPoint: PathChild
780
- ): void {
781
- if (e.buttons !== 1) {
782
- return;
783
- }
1109
+ protected selectionChange(currentPoint: PathChild): void {
784
1110
  // 如果在 selection 里拖动所有点, 没有则切换当前 selection 里的点
785
1111
  const { pathPointSelection } = this;
786
1112
  if (!pathPointSelection) {
787
1113
  return;
788
1114
  }
789
- let selection = pathPointSelection?.selection || [];
1115
+ const selection = pathPointSelection?.selection || [];
790
1116
  const node = this.currentPathNode as Editor2dPathNode;
791
1117
  const selectionNode = [
792
1118
  {
@@ -825,12 +1151,26 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
825
1151
  }
826
1152
  }
827
1153
  }
1154
+ }
1155
+
1156
+ protected onPointMouseDown(
1157
+ e: React.MouseEvent,
1158
+ currentPoint: PathChild
1159
+ ): void {
1160
+ if (e.buttons !== 1) {
1161
+ return;
1162
+ }
1163
+
1164
+ this.selectionChange(currentPoint);
1165
+
1166
+ const node = this.currentPathNode as Editor2dPathNode;
828
1167
  const startNodeClone = deepClone(node.path.paths);
829
1168
 
830
1169
  this.startDrag(e.clientX, e.clientY, {
831
1170
  // 拖动
832
1171
  onDrag: dragEvent => {
833
- selection = pathPointSelection?.selection || [];
1172
+ const { pathPointSelection } = this;
1173
+ const selection = pathPointSelection?.selection || [];
834
1174
  const selectionNodes = selection
835
1175
  .map(s => node.path.paths.find(p => p.id === s.pointId))
836
1176
  .filter(c => c) as PathChild[];
@@ -850,77 +1190,62 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
850
1190
  const startNode = selectionNodes.map((s: PathChild) =>
851
1191
  startNodeClone.find((c: PathChild) => c.id === s.id)
852
1192
  );
853
- const {
854
- position = { x: 0, y: 0 },
855
- scale = { x: 1, y: 1 },
856
- rotation = 0,
857
- } = this.currentPathNode || {};
858
- const offset = this.getBoundsOffset();
859
- let x =
860
- dragEvent.endPos.x / (this.config?.finalScale || 1) -
861
- position.x -
862
- offset.x;
863
- let y =
864
- dragEvent.endPos.y / (this.config?.finalScale || 1) -
865
- position.y -
866
- offset.y;
867
- const center = {
868
- x: -offset.x,
869
- y: -offset.y,
870
- };
871
- const { x: currentX, y: currentY } = inverseTransformToData(
872
- { x, y },
873
- { scale, rotation },
874
- center
875
- );
876
- x = currentX;
877
- y = currentY;
1193
+ const { x, y } = this.canvasPosToPathPos({
1194
+ x: dragEvent.endPos.x / (this.config?.finalScale || 1),
1195
+ y: dragEvent.endPos.y / (this.config?.finalScale || 1),
1196
+ });
878
1197
  selectionNodes.forEach((p: PathChild, i) => {
879
1198
  const start = startNode[i];
880
1199
  const d = delta[i];
881
1200
  const deltaX = x - start.x + d.x;
882
1201
  const deltaY = y - start.y + d.y;
1202
+ const max = Math.max(Math.abs(deltaX), Math.abs(deltaY));
1203
+
1204
+ const isX = max === Math.abs(deltaX);
1205
+ const isY = max === Math.abs(deltaY);
1206
+ const noX = this.shiftKey && !isX;
1207
+ const noY = this.shiftKey && !isY;
883
1208
  if (typeof p.x1 === 'number') {
884
- p.x1 = toFixedValue(start.x1 + deltaX, 2);
1209
+ p.x1 = noX ? start.x1 : toFixedValue(start.x1 + deltaX, 2);
885
1210
  }
886
1211
  if (typeof p.x2 === 'number') {
887
- p.x2 = toFixedValue(start.x2 + deltaX, 2);
1212
+ p.x2 = noX ? start.x2 : toFixedValue(start.x2 + deltaX, 2);
888
1213
  }
889
1214
  if (typeof p.y1 === 'number') {
890
- p.y1 = toFixedValue(start.y1 + deltaY, 2);
1215
+ p.y1 = noY ? start.y1 : toFixedValue(start.y1 + deltaY, 2);
891
1216
  }
892
1217
  if (typeof p.y2 === 'number') {
893
- p.y2 = toFixedValue(start.y2 + deltaY, 2);
1218
+ p.y2 = noY ? start.y2 : toFixedValue(start.y2 + deltaY, 2);
894
1219
  }
895
- p.x = toFixedValue(x + d.x, 2);
896
- p.y = toFixedValue(y + d.y, 2);
1220
+ p.x = noX ? start.x : toFixedValue(x + d.x, 2);
1221
+ p.y = noY ? start.y : toFixedValue(y + d.y, 2);
897
1222
  });
898
1223
 
1224
+ this.updateNodeData(true);
1225
+ },
1226
+ onDragEnd: () => {
899
1227
  this.updateNodeData();
900
1228
  },
901
1229
  });
902
1230
  this.draw();
903
1231
  }
904
- protected onPathAddPoint(i: number, e: React.MouseEvent): void {
905
- if (!this.currentPathNode || !this.document) {
906
- return;
907
- }
908
- const {
909
- path,
910
- position = { x: 0, y: 0 },
911
- scale = { x: 1, y: 1 },
912
- rotation = 0,
913
- } = this.currentPathNode || {};
914
- const { paths = [] } = path;
915
- const p = this.getPosFromMouseEvent(e);
1232
+ // 画布坐标转换成 path 坐标;
1233
+ protected canvasPosToPathPos(p: {
1234
+ x: number;
1235
+ y: number;
1236
+ }): {
1237
+ x: number;
1238
+ y: number;
1239
+ } {
1240
+ const { position = { x: 0, y: 0 }, scale = { x: 1, y: 1 }, rotation = 0 } =
1241
+ this.currentPathNode || {};
916
1242
  const offset = this.getBoundsOffset();
917
- let x = p.x - position.x - offset.x;
918
- let y = p.y - position.y - offset.y;
919
-
920
1243
  const center = {
921
1244
  x: -offset.x,
922
1245
  y: -offset.y,
923
1246
  };
1247
+ let x = p.x - position.x - offset.x;
1248
+ let y = p.y - position.y - offset.y;
924
1249
  const { x: currentX, y: currentY } = inverseTransformToData(
925
1250
  { x, y },
926
1251
  { scale, rotation },
@@ -928,13 +1253,53 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
928
1253
  );
929
1254
  x = currentX;
930
1255
  y = currentY;
931
- const pos = {
1256
+ return {
932
1257
  x,
933
1258
  y,
934
1259
  };
1260
+ }
1261
+ protected pathPosToCanvasPos(c: {
1262
+ x: number;
1263
+ y: number;
1264
+ }): {
1265
+ x: number;
1266
+ y: number;
1267
+ } {
1268
+ const { position = { x: 0, y: 0 }, scale = { x: 1, y: 1 }, rotation = 0 } =
1269
+ this.currentPathNode || {};
1270
+ const offset = this.getBoundsOffset();
1271
+ const center = {
1272
+ x: -offset.x,
1273
+ y: -offset.y,
1274
+ };
1275
+
1276
+ const { x, y } = transformToData(
1277
+ { x: c.x, y: c.y },
1278
+ { scale, rotation },
1279
+ center
1280
+ );
1281
+ return {
1282
+ x: x + position.x + offset.x,
1283
+ y: y + position.y + offset.y,
1284
+ };
1285
+ }
1286
+ protected getPathMousePos(e: React.MouseEvent): { x: number; y: number } {
1287
+ const p = this.getPosFromMouseEvent({
1288
+ clientX: e.clientX ?? 0,
1289
+ clientY: e.clientY ?? 0,
1290
+ });
1291
+ return this.canvasPosToPathPos(p);
1292
+ }
1293
+ protected onPathAddPoint(i: number, e: React.MouseEvent): void {
1294
+ if (!this.currentPathNode || !this.document) {
1295
+ return;
1296
+ }
1297
+ const { path } = this.currentPathNode || {};
1298
+ const { paths = [] } = path;
1299
+ const pos = this.getPathMousePos(e);
935
1300
  const point = paths[i];
936
1301
  const nextPoint = paths[i + 1] ? paths[i + 1] : paths[0];
937
- const newPoint = getNewPoint(point, nextPoint, pos);
1302
+ const newPoint = getNewPoint(point, nextPoint, pos, this.shiftKey);
938
1303
  paths.splice(i + 1, 0, newPoint);
939
1304
  this.pathPointSelection!.enterPathMode([
940
1305
  {
@@ -987,8 +1352,6 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
987
1352
  const paths = [...p].map(c => {
988
1353
  const item = {
989
1354
  ...c,
990
- x: c.x,
991
- y: c.y,
992
1355
  };
993
1356
 
994
1357
  const { x, y } = transformToData(
@@ -1028,6 +1391,8 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
1028
1391
  onBezierPointMouseDown: this.onBezierPointMouseDown.bind(this),
1029
1392
  onClosePath: this.onClosePath.bind(this),
1030
1393
  onPathAddPoint: this.onPathAddPoint.bind(this),
1394
+ isShiftKey: this.shiftKey,
1395
+ isCtrlKey: this.ctrlKey,
1031
1396
  }}
1032
1397
  >
1033
1398
  <SvgPath
@@ -1035,11 +1400,15 @@ export class PathEditLayer extends Layer<PlaygroundContext2d> {
1035
1400
  height={this.config.config.pageBounds?.height || 300}
1036
1401
  getPosFromMouseEvent={this.getPosFromMouseEvent.bind(this)}
1037
1402
  node={this.currentPathNode}
1403
+ showAllBezier={this.altKey}
1038
1404
  paths={paths}
1039
1405
  selection={this.pathPointSelection?.selection as PathPointSelection[]}
1040
1406
  selectMode={this.pathPointSelection?.selectMode}
1041
1407
  getPointIsStartOrEnd={this.getPointIsStartOrEnd.bind(this)}
1408
+ onUpdateNodeData={this.updateNodeData.bind(this)}
1042
1409
  scale={this.config.finalScale}
1410
+ getPathMousePos={this.getPathMousePos.bind(this)}
1411
+ pathPosToCanvasPos={this.pathPosToCanvasPos.bind(this)}
1043
1412
  />
1044
1413
  </PathEditLayerEventContext.Provider>
1045
1414
  );