Skip to content

Commit

Permalink
feat: use dragndrop to move shape
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoiver committed Nov 28, 2024
1 parent 2e89073 commit 509f32b
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 48 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ cd packages/lesson_001
pnpm run dev
```

Or you can run the site locally:

```bash
pnpm run build
cd packages/site
pnpm run dev
```

## Lesson 1 - Initialize canvas [🔗](https://infinitecanvas.cc/guide/lesson-001)

- A hardware abstraction layer based on WebGL1/2 and WebGPU.
Expand Down Expand Up @@ -156,6 +164,7 @@ pnpm run dev

## Lesson 14 - Canvas mode [🔗](https://infinitecanvas.cc/guide/lesson-014)

- Implement `zIndex` and `sizeAttenuation`
- Add more canvas modes, e.g. move, select and shapes

[infinitecanvas]: https://infinitecanvas.tools/
Expand Down
9 changes: 9 additions & 0 deletions README.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ cd packages/lesson_001
pnpm run dev
```

或者你也可以本地运行这个教程站点

```bash
pnpm run build
cd packages/site
pnpm run dev
```

## 课程 1 - 初始化画布 [🔗](https://infinitecanvas.cc/zh/guide/lesson-001)

- 基于 WebGL1/2 和 WebGPU 的硬件抽象层
Expand Down Expand Up @@ -158,6 +166,7 @@ pnpm run dev

## 课程 14 - 画布模式 [🔗](https://infinitecanvas.cc/zh/guide/lesson-014)

- 实现 `zIndex``sizeAttenuation`
- 增加画布模式,支持移动、选取、添加图形等。

[infinitecanvas]: https://infinitecanvas.tools/
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/plugins/Selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,14 @@ export class Selector implements Plugin {
}
};

const handleResizedTarget = (e: CustomEvent) => {
// const target = e.target as Shape;
};

root.addEventListener('click', handleClick);
root.addEventListener(SelectableEvent.MOVING, handleMovingTarget);
root.addEventListener(SelectableEvent.MOVED, handleMovedTarget);
root.addEventListener(SelectableEvent.RESIZED, handleResizedTarget);

root.appendChild(this.#activeSelectableLayer);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/shapes/selectable/AbstractSelectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export enum SelectableEvent {
DESELECTED = 'deselected',

/**
* resized or definition changed
* resized
*/
MODIFIED = 'modified',
RESIZED = 'resized',

/**
* dragend
Expand Down
70 changes: 28 additions & 42 deletions packages/core/src/shapes/selectable/SelectableRect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,15 @@ export class SelectableRect extends AbstractSelectable {
});

// move mask
this.mask.position.x += dx;
this.mask.position.y += dy;
this.mask.position.x = canvasX - shiftX;
this.mask.position.y = canvasY - shiftY;
};

this.addEventListener('dragstart', (e: FederatedPointerEvent) => {
const target = e.target as Shape;

if (target === this.mask) {
const { x, y } = this.mask.position;

shiftX = e.screen.x - x;
shiftY = e.screen.y - y;

moveAt(e.screen.x, e.screen.y);
shiftX = e.screen.x;
shiftY = e.screen.y;
}
});

Expand All @@ -146,7 +141,15 @@ export class SelectableRect extends AbstractSelectable {
target === this.blAnchor ||
target === this.brAnchor
) {
// TODO: account for target's rotation
if (target === this.tlAnchor) {
const { cx: brCx, cy: brCy } = this.brAnchor;
this.tlAnchor.cx = x;
this.tlAnchor.cy = y;
this.trAnchor.cx = brCx;
this.trAnchor.cy = y;
this.blAnchor.cx = x;
this.blAnchor.cy = brCy;
} else if (target === this.trAnchor) {
const { cx: blCx, cy: blCy } = this.blAnchor;
this.trAnchor.cx = x;
Expand All @@ -155,23 +158,25 @@ export class SelectableRect extends AbstractSelectable {
this.tlAnchor.cy = y;
this.brAnchor.cx = x;
this.brAnchor.cy = blCy;
this.mask;
} else if (target === this.blAnchor) {
const { cx: trCx, cy: trCy } = this.trAnchor;
this.blAnchor.cx = x;
this.blAnchor.cy = y;
this.brAnchor.cx = trCx;
this.brAnchor.cy = y;
this.tlAnchor.cx = x;
this.tlAnchor.cy = trCy;
} else if (target === this.brAnchor) {
const { cx: tlCx, cy: tlCy } = this.tlAnchor;
this.brAnchor.cx = x;
this.brAnchor.cy = y;
this.blAnchor.cx = tlCx;
this.blAnchor.cy = y;
this.trAnchor.cx = x;
this.trAnchor.cy = tlCy;
}
// // resize mask
// this.mask.d = `M${maskX} ${maskY}L${maskX + maskWidth} ${maskY}L${
// maskX + maskWidth
// } ${maskY + maskHeight}L${maskX} ${maskY + maskHeight}Z`;
// // re-position anchors
// this.tlAnchor.cx = maskX;
// this.tlAnchor.cy = maskY;
// this.trAnchor.cx = maskX + maskWidth;
// this.trAnchor.cy = maskY;
// this.blAnchor.cx = maskX;
// this.blAnchor.cy = maskY + maskHeight;
// this.brAnchor.cx = maskX + maskWidth;
// this.brAnchor.cy = maskY + maskHeight;

this.mask.d = `M${this.tlAnchor.cx} ${this.tlAnchor.cy}L${this.trAnchor.cx} ${this.trAnchor.cy}L${this.brAnchor.cx} ${this.brAnchor.cy}L${this.blAnchor.cx} ${this.blAnchor.cy}Z`;
}
});

Expand All @@ -198,28 +203,9 @@ export class SelectableRect extends AbstractSelectable {
const { cx: brCx, cy: brCy } = this.brAnchor;
const { cx: blCx, cy: blCy } = this.blAnchor;

console.log(tlCx, tlCy, trCx, trCy, brCx, brCy, blCx, blCy);
console.log(this.mask.position.x, this.mask.position.y);

this.mask.position.x = 0;
this.mask.position.y = 0;
this.mask.d = `M${tlCx} ${tlCy}L${trCx} ${trCy}L${brCx} ${brCy}L${blCx} ${blCy}Z`;
} else if (
target === this.tlAnchor ||
target === this.trAnchor ||
target === this.blAnchor ||
target === this.brAnchor
) {
// targetObject.dispatchEvent(
// new CustomEvent(SelectableEvent.MODIFIED, {
// rect: {
// x: maskX,
// y: maskY,
// width: maskWidth,
// height: maskHeight,
// },
// }),
// );
}
});
}
Expand Down
64 changes: 60 additions & 4 deletions packages/site/docs/zh/guide/lesson-014.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,8 @@ call(() => {
const canvas = e.detail;

const ellipse = new RoughEllipse({
cx: 0,
cy: 0,
cx: 200,
cy: 100,
rx: 50,
ry: 50,
fill: 'black',
Expand All @@ -490,16 +490,71 @@ call(() => {

按住 `Shift` 可以进行多选。

### 合并选中成组 {#group-selection}
### 拖拽移动图形 {#dragndrop-move}

HTML 原生是支持拖拽的,当然我们使用更底层的事件例如 `pointermove / up / down` 也是可以实现的,详见:[Drag'n'Drop with mouse events],我们的实现也借鉴了这篇文章的思路。在 `dragstart` 事件中记录下鼠标在画布上的偏移量,注意这里使用的是 `screen` 坐标系,因为要考虑到相机缩放的情况:

```ts
let shiftX = 0;
let shiftY = 0;
this.addEventListener('dragstart', (e: FederatedPointerEvent) => {
const target = e.target as Shape;
if (target === this.mask) {
shiftX = e.screen.x;
shiftY = e.screen.y;
}
});
```

`drag` 事件中根据该偏移量调整蒙层位置,使用 `position` 属性不必修改 mask 的路径定义,反映到底层渲染中只有 `u_ModelMatrix` 会发生改变:

```ts
const moveAt = (canvasX: number, canvasY: number) => {
const { x, y } = this.mask.position;
const dx = canvasX - shiftX - x;
const dy = canvasY - shiftY - y;

this.mask.position.x += dx;
this.mask.position.y += dy;
};

this.addEventListener('drag', (e: FederatedPointerEvent) => {
const target = e.target as Shape;
const { x, y } = e.screen;

### 拖拽移动图形 {#dragndrop}
if (target === this.mask) {
moveAt(x, y);
}
});
```

`dragend` 事件中,将蒙层位置同步到图形上,此时才会修改蒙层的路径:

```ts
this.addEventListener('dragend', (e: FederatedEvent) => {
const target = e.target as Shape;
if (target === this.mask) {
this.tlAnchor.cx += this.mask.position.x;
this.tlAnchor.cy += this.mask.position.y;

const { cx: tlCx, cy: tlCy } = this.tlAnchor;

this.mask.position.x = 0;
this.mask.position.y = 0;
this.mask.d = `M${tlCx} ${tlCy}L${trCx} ${trCy}L${brCx} ${brCy}L${blCx} ${blCy}Z`;
}
});
```

### 合并选中成组 {#group-selection}

## 绘制模式 {#draw-mode}

## 扩展阅读 {#extended-reading}

- [Excalidraw ToolType]
- [Introducing the CSS anchor positioning API]
- [Drag'n'Drop with mouse events]

[画布 UI 组件]: /zh/guide/lesson-007
[Introducing the CSS anchor positioning API]: https://developer.chrome.com/blog/anchor-positioning-api
Expand All @@ -520,3 +575,4 @@ call(() => {
[Scalars]: https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Scalars
[Single-precision floating-point format]: https://en.wikipedia.org/wiki/Single-precision_floating-point_format
[Graphics Tech in Cesium - Vertex Compression]: https://cesium.com/blog/2015/05/18/vertex-compression/
[Drag'n'Drop with mouse events]: https://javascript.info/mouse-drag-and-drop

0 comments on commit 509f32b

Please sign in to comment.