Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Layer', () => {
<div id="div"></div>
<textarea id="text"></textarea>
</PlaygroundReactRenderer>
</PlaygroundReactProvider>,
</PlaygroundReactProvider>
);
});

Expand All @@ -46,7 +46,7 @@ describe('Layer', () => {
registry.renderer.node.parentNode!.dispatchEvent(
new MouseEvent('mousedown', {
button: 1,
}),
})
);
expect(editorStateConfig.is(EditorState.STATE_GRAB.id)).toBe(true);

Expand All @@ -56,7 +56,7 @@ describe('Layer', () => {
// 切换鼠标模式
editorStateConfig?.changeState(
EditorState.STATE_MOUSE_FRIENDLY_SELECT.id,
new MouseEvent('mousedown') as any,
new MouseEvent('mousedown') as any
);
expect(editorStateConfig.is(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id)).toBe(true);

Expand All @@ -71,7 +71,7 @@ describe('Layer', () => {
shiftKey: true,
bubbles: true,
cancelable: true,
}),
})
);

// 鼠标变成箭头
Expand All @@ -85,7 +85,7 @@ describe('Layer', () => {
shiftKey: true,
bubbles: true,
cancelable: true,
}),
})
);

// 触发滚动事件
Expand All @@ -94,7 +94,7 @@ describe('Layer', () => {
deltaY: 100, // 向下滚动 100,
bubbles: true,
cancelable: true,
} as any),
} as any)
);

// 切换触控板
Expand All @@ -108,16 +108,35 @@ describe('Layer', () => {
code: 'Space',
bubbles: true,
cancelable: true,
}),
})
);
expect(editorStateConfig.isPressingSpaceBar).toBe(true);

const twoFingerTouchStartEvent = new Event('touchstart', {
bubbles: true,
cancelable: true,
}) as TouchEvent;
Object.defineProperties(twoFingerTouchStartEvent, {
touches: {
value: [
{ clientX: 100, clientY: 100 },
{ clientX: 200, clientY: 200 },
],
},
changedTouches: { value: [] },
});

registry.renderer.node.parentNode!.dispatchEvent(twoFingerTouchStartEvent);

expect(playgroundLayer.config.config.scrollX).toBe(0);
expect(playgroundLayer.config.config.scrollY).toBe(0);

// 开始拖拽
registry.renderer.node.parentNode!.dispatchEvent(
new MouseEvent('mousedown', {
clientX: 100,
clientY: 100,
}),
})
);

// 注销 layer
Expand Down
30 changes: 28 additions & 2 deletions packages/canvas-engine/core/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ describe('utils', () => {
// 触发滚动
playgroundDrag.fireScroll('scrollX', true);
const dispose = startDrag(20, 20, {
onDragStart: e => {
onDragStart: (e) => {
expect(e?.startPos).toEqual({ x: 20, y: 20 });
},
onDragEnd: e => {
onDragEnd: (e) => {
expect(e?.startPos).toEqual({ x: 20, y: 20 });
expect(e?.endPos).toEqual({ x: 0, y: 0 });
},
Expand All @@ -100,6 +100,32 @@ describe('utils', () => {
playgroundDrag.start(100, 100, playground.config);
});

it('PlaygroundDrag should stop when a second touch joins', () => {
const onDragEnd = vi.fn();
const dragger = new PlaygroundDrag<any>({
onDragEnd,
});
const touches = [
{ clientX: 20, clientY: 30, screenX: 20, screenY: 30 },
{ clientX: 120, clientY: 130, screenX: 120, screenY: 130 },
];
const touchMoveEvent = new Event('touchmove', {
bubbles: true,
cancelable: true,
}) as TouchEvent;

Object.defineProperties(touchMoveEvent, {
touches: { value: touches },
changedTouches: { value: [] },
});

dragger.start(10, 10);
dragger.handleEvent(touchMoveEvent);

expect(onDragEnd).toHaveBeenCalledTimes(1);
expect(dragger.isStarted).toBe(false);
});

it('mock mouse event', () => {
const playground = createPlayground();
playground.ready();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ export class PlaygroundLayer extends Layer<PlaygroundLayerOptions> {
this.listenPlaygroundEvent(
'touchstart',
(e: TouchEvent) => {
if (e.touches.length > 1) {
return;
}
const { clientX: x, clientY: y } = MouseTouchEvent.getEventCoord(e);
if (!this.options?.hoverService) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ export class PlaygroundDrag<T = undefined> implements Disposable {
}

handleEvent(_event: Event): void {
if (MouseTouchEvent.isTouchEvent(_event as TouchEvent)) {
const touchEvent = _event as TouchEvent;
if (touchEvent.touches.length > 1) {
const { clientX, clientY } = MouseTouchEvent.getEventCoord(touchEvent);
this._evtMouseUp(createMouseEvent('mouseup', clientX, clientY));
return;
}
}
const event = MouseTouchEvent.touchToMouseEvent(_event);
switch (event.type) {
case 'mousemove':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ describe('PlaygroundGesture', () => {
scrollY: 0,
},
finalScale: 1,
zoomEnable: true,
zoomDisable: false,
playgroundDomNode: {
getBoundingClientRect: vi.fn().mockReturnValue({ x: 0, y: 0 }),
},
getPosFromMouseEvent: vi.fn().mockReturnValue({ x: 50, y: 50 }),
updateConfig: vi.fn(),
};
Expand Down Expand Up @@ -131,5 +136,47 @@ describe('PlaygroundGesture', () => {
zoom: 2,
});
});

it('should keep the pinch origin fixed around the two-finger center', () => {
config.playgroundDomNode.getBoundingClientRect.mockReturnValue({ x: 20, y: 30 });
config.finalScale = 2;
config.config.scrollX = 40;
config.config.scrollY = 60;
config.config.maxZoom = 4;

playgroundGesture.handlePinch({
first: false,
last: false,
originX: 220,
originY: 330,
newScale: 3,
});

const nextConfig = config.updateConfig.mock.calls[0][0];
expect(nextConfig.scrollX).toBeCloseTo(160);
expect(nextConfig.scrollY).toBeCloseTo(240);
expect(nextConfig.zoom).toBe(3);
});

it('should calculate scroll with the normalized scale', () => {
config.playgroundDomNode.getBoundingClientRect.mockReturnValue({ x: 20, y: 30 });
config.finalScale = 2;
config.config.scrollX = 40;
config.config.scrollY = 60;
config.config.maxZoom = 2.2;

playgroundGesture.handlePinch({
first: false,
last: false,
originX: 220,
originY: 330,
newScale: 3,
});

const nextConfig = config.updateConfig.mock.calls[0][0];
expect(nextConfig.scrollX).toBeCloseTo(64);
expect(nextConfig.scrollY).toBeCloseTo(96);
expect(nextConfig.zoom).toBe(2.2);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,22 @@ export class PlaygroundGesture extends DisposableImpl {
if (last) {
this._pinching = false;
}
const { scrollX, scrollY } = this.config.config;
const oldScale = this.config.finalScale;
const origin = this.config.getPosFromMouseEvent({ clientX: originX, clientY: originY }, false);
// 放大后的位置
const finalPos = {
x: (origin.x / oldScale) * newScale,
y: (origin.y / oldScale) * newScale,
const scale = this.getNormalizedScale(newScale);
const clientRect = this.config.playgroundDomNode.getBoundingClientRect();
const originOffset = {
x: originX - clientRect.x,
y: originY - clientRect.y,
};
const origin = {
x: (originOffset.x + scrollX) / oldScale,
y: (originOffset.y + scrollY) / oldScale,
};
this.config.updateConfig({
scrollX: this.config.config.scrollX + finalPos.x - origin.x,
scrollY: this.config.config.scrollY + finalPos.y - origin.y,
zoom: newScale,
scrollX: origin.x * scale - originOffset.x,
scrollY: origin.y * scale - originOffset.y,
zoom: scale,
});
}

Expand All @@ -95,6 +100,16 @@ export class PlaygroundGesture extends DisposableImpl {
};
}

protected getNormalizedScale(scale: number): number {
if (!this.config.zoomEnable) {
return 1;
}
if (this.config.zoomDisable) {
return this.config.config.zoom;
}
return Math.max(this.config.config.minZoom, Math.min(this.config.config.maxZoom, scale));
}

protected preventDefault(): void {
// 阻止默认手势
const handler = (e: MouseEvent) => e.preventDefault();
Expand Down
Loading