diff --git a/blocksuite/affine/gfx/pointer/src/snap/snap-manager.ts b/blocksuite/affine/gfx/pointer/src/snap/snap-manager.ts index dd49fd1567..e90c69c962 100644 --- a/blocksuite/affine/gfx/pointer/src/snap/snap-manager.ts +++ b/blocksuite/affine/gfx/pointer/src/snap/snap-manager.ts @@ -1,6 +1,6 @@ import { OverlayIdentifier } from '@blocksuite/affine-block-surface'; import { MindmapElementModel } from '@blocksuite/affine-model'; -import { Bound } from '@blocksuite/global/gfx'; +import { type Bound } from '@blocksuite/global/gfx'; import { type DragExtensionInitializeContext, type ExtensionDragMoveContext, @@ -74,47 +74,63 @@ export class SnapExtension extends InteractivityExtension { return {}; } - let alignBound: Bound | null = null; - return { onResizeStart(context) { - alignBound = snapOverlay.setMovingElements(context.elements); + snapOverlay.setMovingElements(context.elements); }, onResizeMove(context) { - if (!alignBound || alignBound.w === 0 || alignBound.h === 0) { - return; + const { + handle, + originalBound, + scaleX, + scaleY, + handleSign, + currentHandlePos, + elements, + } = context; + const rotate = elements.length > 1 ? 0 : elements[0].rotate; + const alignDirection: ('vertical' | 'horizontal')[] = []; + let switchDirection = false; + let nx = handleSign.x; + let ny = handleSign.y; + + if (handle.length > 6) { + alignDirection.push('vertical', 'horizontal'); + } else if (rotate % 90 === 0) { + nx = + handleSign.x * Math.cos((rotate / 180) * Math.PI) - + handleSign.y * Math.sin((rotate / 180) * Math.PI); + ny = + handleSign.x * Math.sin((rotate / 180) * Math.PI) + + handleSign.y * Math.cos((rotate / 180) * Math.PI); + + if (Math.abs(nx) > Math.abs(ny)) { + alignDirection.push('horizontal'); + } else { + alignDirection.push('vertical'); + } + + if (rotate % 180 !== 0) { + switchDirection = true; + } } - const { handle, handleSign, lockRatio } = context; - let { dx, dy } = context; - - if (lockRatio) { - const min = Math.min( - Math.abs(dx / alignBound.w), - Math.abs(dy / alignBound.h) + if (alignDirection.length > 0) { + const rst = snapOverlay.alignResize( + currentHandlePos, + alignDirection ); - dx = min * Math.sign(dx) * alignBound.w; - dy = min * Math.sign(dy) * alignBound.h; + const dx = switchDirection ? ny * rst.dy : nx * rst.dx; + const dy = switchDirection ? nx * rst.dx : ny * rst.dy; + + context.suggest({ + scaleX: scaleX + dx / originalBound.w, + scaleY: scaleY + dy / originalBound.h, + }); } - - const currentBound = new Bound( - alignBound.x + - (handle.includes('left') ? -dx * handleSign.xSign : 0), - alignBound.y + - (handle.includes('top') ? -dy * handleSign.ySign : 0), - Math.abs(alignBound.w + dx * handleSign.xSign), - Math.abs(alignBound.h + dy * handleSign.ySign) - ); - const alignRst = snapOverlay.align(currentBound); - - context.suggest({ - dx: alignRst.dx + context.dx, - dy: alignRst.dy + context.dy, - }); }, onResizeEnd() { - alignBound = null; snapOverlay.clear(); }, }; diff --git a/blocksuite/affine/gfx/pointer/src/snap/snap-overlay.ts b/blocksuite/affine/gfx/pointer/src/snap/snap-overlay.ts index 86e1ec2fc5..a18adfe2c8 100644 --- a/blocksuite/affine/gfx/pointer/src/snap/snap-overlay.ts +++ b/blocksuite/affine/gfx/pointer/src/snap/snap-overlay.ts @@ -3,7 +3,7 @@ import { ConnectorElementModel, MindmapElementModel, } from '@blocksuite/affine-model'; -import { almostEqual, Bound, Point } from '@blocksuite/global/gfx'; +import { almostEqual, Bound, type IVec, Point } from '@blocksuite/global/gfx'; import type { GfxModel } from '@blocksuite/std/gfx'; interface Distance { @@ -586,6 +586,60 @@ export class SnapOverlay extends Overlay { ); } + alignResize(position: IVec, direction: ('vertical' | 'horizontal')[]) { + const rst = { dx: 0, dy: 0 }; + + const { viewport } = this.gfx; + const threshold = ALIGN_THRESHOLD / viewport.zoom; + const searchBound = new Bound( + position[0] - threshold / 2, + position[1] - threshold / 2, + threshold, + threshold + ); + const alignBound = new Bound(position[0], position[1], 0, 0); + + this._intraGraphicAlignLines = { + horizontal: [], + vertical: [], + }; + this._distributedAlignLines = []; + this._updateAlignCandidates(searchBound); + + for (const other of this._referenceBounds.all) { + const closestDistances = this._calculateClosestDistances( + alignBound, + other + ); + + if ( + direction.includes('horizontal') && + closestDistances.horiz && + (!this._intraGraphicAlignLines.horizontal.length || + Math.abs(closestDistances.horiz.distance) < Math.abs(rst.dx)) + ) { + this._updateXAlignPoint(rst, alignBound, other, closestDistances); + } + + if ( + direction.includes('vertical') && + closestDistances.vert && + (!this._intraGraphicAlignLines.vertical.length || + Math.abs(closestDistances.vert.distance) < Math.abs(rst.dy)) + ) { + this._updateYAlignPoint(rst, alignBound, other, closestDistances); + } + } + + this._intraGraphicAlignLines.horizontal = + this._intraGraphicAlignLines.horizontal.slice(0, 1); + this._intraGraphicAlignLines.vertical = + this._intraGraphicAlignLines.vertical.slice(0, 1); + this._renderer?.refresh(); + + return rst; + } + align(bound: Bound): { dx: number; dy: number } { const rst = { dx: 0, dy: 0 }; const threshold = ALIGN_THRESHOLD / this.gfx.viewport.zoom; diff --git a/blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-selected-rect.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-selected-rect.ts index a2d233a05c..be6eae2e59 100644 --- a/blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-selected-rect.ts +++ b/blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-selected-rect.ts @@ -374,6 +374,8 @@ export class EdgelessSelectedRectWidget extends WidgetComponent type: 'resize' | 'rotate'; angle: number; handle: ResizeHandle; + flipX?: boolean; + flipY?: boolean; pure?: boolean; }) => { if (!options) { @@ -381,8 +383,25 @@ export class EdgelessSelectedRectWidget extends WidgetComponent return 'default'; } - const { type, angle, handle } = options; + const { type, angle, flipX, flipY } = options; let cursor: CursorType = 'default'; + let handle: ResizeHandle = options.handle; + + if (flipX) { + handle = ( + handle.includes('left') + ? handle.replace('left', 'right') + : handle.replace('right', 'left') + ) as ResizeHandle; + } + + if (flipY) { + handle = ( + handle.includes('top') + ? handle.replace('top', 'bottom') + : handle.replace('bottom', 'top') + ) as ResizeHandle; + } if (type === 'rotate') { cursor = generateCursorUrl(angle, handle); @@ -626,7 +645,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent onResizeStart: () => { this._mode = 'resize'; }, - onResizeUpdate: ({ lockRatio, scaleX, exceed }) => { + onResizeUpdate: ({ lockRatio, scaleX, scaleY, exceed }) => { if (lockRatio) { this._scaleDirection = handle; this._scalePercent = `${Math.round(scaleX * 100)}%`; @@ -642,6 +661,8 @@ export class EdgelessSelectedRectWidget extends WidgetComponent type: 'resize', angle: elements.length > 1 ? 0 : (elements[0]?.rotate ?? 0), handle, + flipX: scaleX < 0, + flipY: scaleY < 0, }); }, onResizeEnd: () => { @@ -652,6 +673,14 @@ export class EdgelessSelectedRectWidget extends WidgetComponent } }, option => { + if ( + ['resize', 'rotate'].includes( + interaction.activeInteraction$.value?.type ?? '' + ) + ) { + return ''; + } + return this._updateCursor({ ...option, angle: elements.length > 1 ? 0 : (elements[0]?.rotate ?? 0), diff --git a/blocksuite/framework/std/src/gfx/interactivity/manager.ts b/blocksuite/framework/std/src/gfx/interactivity/manager.ts index 7837dd550b..11e101583e 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/manager.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/manager.ts @@ -947,23 +947,34 @@ export class InteractivityManager extends GfxExtension { ...options, lockRatio, elements, - onResizeMove: ({ dx, dy, handleSign, lockRatio }) => { + onResizeMove: ({ + scaleX, + scaleY, + originalBound, + handleSign, + handlePos, + currentHandlePos, + lockRatio, + }) => { const suggested: { - dx: number; - dy: number; + scaleX: number; + scaleY: number; priority?: number; }[] = []; - const suggest = (distance: { dx: number; dy: number }) => { + const suggest = (distance: { scaleX: number; scaleY: number }) => { suggested.push(distance); }; extensionHandlers.forEach(ext => { ext.onResizeMove?.({ - dx, - dy, + scaleX, + scaleY, elements, - handleSign, handle, + handleSign, + handlePos, + originalBound, + currentHandlePos, lockRatio, suggest, }); @@ -973,9 +984,9 @@ export class InteractivityManager extends GfxExtension { return (a.priority ?? 0) - (b.priority ?? 0); }); - return last(suggested) ?? { dx, dy }; + return last(suggested) ?? { scaleX, scaleY }; }, - onResizeStart: ({ data }) => { + onResizeStart: ({ handleSign, handlePos, data }) => { this.activeInteraction$.value = { type: 'resize', elements, @@ -984,6 +995,8 @@ export class InteractivityManager extends GfxExtension { ext.onResizeStart?.({ elements, handle, + handlePos, + handleSign, }); }); @@ -1045,13 +1058,15 @@ export class InteractivityManager extends GfxExtension { options.onResizeUpdate?.({ scaleX, scaleY, lockRatio, exceed }); }, - onResizeEnd: ({ data }) => { + onResizeEnd: ({ handleSign, handlePos, data }) => { this.activeInteraction$.value = null; extensionHandlers.forEach(ext => { ext.onResizeEnd?.({ elements, handle, + handlePos, + handleSign, }); }); options.onResizeEnd?.(); diff --git a/blocksuite/framework/std/src/gfx/interactivity/resize/manager.ts b/blocksuite/framework/std/src/gfx/interactivity/resize/manager.ts index 44a43f39e4..a5b3cb4a26 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/resize/manager.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/resize/manager.ts @@ -2,6 +2,7 @@ import { Bound, getCommonBoundWithRotation, type IBound, + type IPoint, type IVec, } from '@blocksuite/global/gfx'; @@ -29,7 +30,7 @@ export const DEFAULT_HANDLES: ResizeHandle[] = [ 'bottom', ]; -type ElementInitialSnapshot = Readonly>; +type ReadonlyIBound = Readonly>; export interface OptionResize { elements: GfxModel[]; @@ -37,16 +38,18 @@ export interface OptionResize { lockRatio: boolean; event: PointerEvent; onResizeMove: (payload: { - dx: number; - dy: number; + scaleX: number; + scaleY: number; - handleSign: { - xSign: number; - ySign: number; - }; + originalBound: IBound; + + handleSign: IPoint; + + handlePos: IVec; + currentHandlePos: IVec; lockRatio: boolean; - }) => { dx: number; dy: number }; + }) => { scaleX: number; scaleY: number }; onResizeUpdate: (payload: { lockRatio: boolean; scaleX: number; @@ -59,8 +62,16 @@ export interface OptionResize { matrix: DOMMatrix; }[]; }) => void; - onResizeStart?: (payload: { data: { model: GfxModel }[] }) => void; - onResizeEnd?: (payload: { data: { model: GfxModel }[] }) => void; + onResizeStart?: (payload: { + handlePos: IVec; + handleSign: IPoint; + data: { model: GfxModel }[]; + }) => void; + onResizeEnd?: (payload: { + handlePos: IVec; + handleSign: IPoint; + data: { model: GfxModel }[]; + }) => void; } export type RotateOption = { @@ -95,11 +106,102 @@ export class ResizeController { this.gfx = option.gfx; } + getCoordsTransform(originalBound: IBound, handle: ResizeHandle) { + const { x: xSign, y: ySign } = this.getHandleSign(handle); + const pivot = new DOMPoint( + originalBound.x + ((-xSign + 1) / 2) * originalBound.w, + originalBound.y + ((-ySign + 1) / 2) * originalBound.h + ); + const toLocalM = new DOMMatrix().translate(-pivot.x, -pivot.y); + const toLocalRotatedM = new DOMMatrix() + .translate(-pivot.x, -pivot.y) + .translate( + originalBound.w / 2 + originalBound.x, + originalBound.h / 2 + originalBound.y + ) + .rotate(-(originalBound.rotate ?? 0)) + .translate( + -(originalBound.w / 2 + originalBound.x), + -(originalBound.h / 2 + originalBound.y) + ); + + const toLocal = (p: DOMPoint, withRotation: boolean = false) => + p.matrixTransform(withRotation ? toLocalRotatedM : toLocalM); + const toModel = (p: DOMPoint) => + p.matrixTransform(toLocalRotatedM.inverse()); + + const handlePos = toModel( + new DOMPoint(originalBound.w * xSign, originalBound.h * ySign) + ); + + return { + xSign, + ySign, + originalBound, + toLocalM, + toLocalRotatedM, + toLocal, + toModel, + handlePos: [handlePos.x, handlePos.y] as IVec, + }; + } + + getScaleFromDelta( + transform: ReturnType, + delta: { dx: number; dy: number }, + handleStartPos: IVec, + lockRatio: boolean + ) { + const { originalBound, xSign, ySign, toModel, toLocal } = transform; + const currentPos = toLocal( + new DOMPoint(handleStartPos[0] + delta.dx, handleStartPos[1] + delta.dy), + true + ); + + let scaleX = xSign ? currentPos.x / (originalBound.w * xSign) : 1; + let scaleY = ySign ? currentPos.y / (originalBound.h * ySign) : 1; + + if (lockRatio) { + const min = Math.min(Math.abs(scaleX), Math.abs(scaleY)); + scaleX = Math.sign(scaleX) * min; + scaleY = Math.sign(scaleY) * min; + } + + const finalHandlePos = toModel( + new DOMPoint( + originalBound.w * xSign * scaleX, + originalBound.h * ySign * scaleY + ) + ); + + return { + scaleX, + scaleY, + handlePos: [finalHandlePos.x, finalHandlePos.y] as IVec, + }; + } + + getScaleMatrix( + { scaleX, scaleY }: { scaleX: number; scaleY: number }, + lockRatio: boolean + ) { + if (lockRatio) { + const min = Math.min(Math.abs(scaleX), Math.abs(scaleY)); + scaleX = Math.sign(scaleX) * min; + scaleY = Math.sign(scaleY) * min; + } + + return { + scaleX, + scaleY, + scaleM: new DOMMatrix().scaleSelf(scaleX, scaleY), + }; + } + startResize(options: OptionResize) { const { elements, handle, - lockRatio, onResizeStart, onResizeMove, onResizeUpdate, @@ -107,19 +209,32 @@ export class ResizeController { event, } = options; - const originals: ElementInitialSnapshot[] = elements.map(el => ({ + const originals: ReadonlyIBound[] = elements.map(el => ({ x: el.x, y: el.y, w: el.w, h: el.h, rotate: el.rotate, })); - const originalBound = getCommonBoundWithRotation(originals); + const originalBound: IBound = + originals.length > 1 + ? getCommonBoundWithRotation(originals) + : { + x: originals[0].x, + y: originals[0].y, + w: originals[0].w, + h: originals[0].h, + rotate: originals[0].rotate, + }; const startPt = this.gfx.viewport.toModelCoordFromClientCoord([ event.clientX, event.clientY, ]); - const handleSign = this.getHandleSign(handle); + const transform = this.getCoordsTransform(originalBound, handle); + const handleSign = { + x: transform.xSign, + y: transform.ySign, + }; const onPointerMove = (e: PointerEvent) => { const currPt = this.gfx.viewport.toModelCoordFromClientCoord([ @@ -130,45 +245,69 @@ export class ResizeController { dx: currPt[0] - startPt[0], dy: currPt[1] - startPt[1], }; - const shouldLockRatio = lockRatio || e.shiftKey; + const shouldLockRatio = + options.lockRatio || e.shiftKey || elements.length > 1; + const { + scaleX, + scaleY, + handlePos: currentHandlePos, + } = this.getScaleFromDelta( + transform, + delta, + transform.handlePos, + shouldLockRatio + ); + + const scale = onResizeMove({ + scaleX, + scaleY, + + originalBound, - delta = onResizeMove({ - dx: delta.dx, - dy: delta.dy, handleSign, + + handlePos: transform.handlePos, + currentHandlePos, + lockRatio: shouldLockRatio, }); + const scaleInfo = this.getScaleMatrix(scale, shouldLockRatio); if (elements.length === 1) { this.resizeSingle( originals[0], elements[0], shouldLockRatio, - startPt, - delta, - handleSign, + transform, + scaleInfo, onResizeUpdate ); } else { this.resizeMulti( - originalBound, originals, elements, - startPt, - delta, - handleSign, + transform, + scaleInfo, onResizeUpdate ); } }; - onResizeStart?.({ data: elements.map(model => ({ model })) }); + onResizeStart?.({ + handleSign, + handlePos: transform.handlePos, + data: elements.map(model => ({ model })), + }); const onPointerUp = () => { this.host.removeEventListener('pointermove', onPointerMove); this.host.removeEventListener('pointerup', onPointerUp); - onResizeEnd?.({ data: elements.map(model => ({ model })) }); + onResizeEnd?.({ + handleSign, + handlePos: transform.handlePos, + data: elements.map(model => ({ model })), + }); }; this.host.addEventListener('pointermove', onPointerMove); @@ -176,55 +315,15 @@ export class ResizeController { } private resizeSingle( - orig: ElementInitialSnapshot, + orig: ReadonlyIBound, model: GfxModel, lockRatio: boolean, - startPt: IVec, - delta: { - dx: number; - dy: number; - }, - handleSign: { xSign: number; ySign: number }, + transform: ReturnType, + scale: { scaleX: number; scaleY: number; scaleM: DOMMatrix }, updateCallback: OptionResize['onResizeUpdate'] ) { - const { xSign, ySign } = handleSign; - - const pivot = new DOMPoint( - orig.x + (-xSign === 1 ? orig.w : 0), - orig.y + (-ySign === 1 ? orig.h : 0) - ); - const toLocalRotatedM = new DOMMatrix() - .translate(-pivot.x, -pivot.y) - .translate(orig.w / 2 + orig.x, orig.h / 2 + orig.y) - .rotate(-orig.rotate) - .translate(-(orig.w / 2 + orig.x), -(orig.h / 2 + orig.y)); - const toLocalM = new DOMMatrix().translate(-pivot.x, -pivot.y); - - const toLocal = (p: DOMPoint, withRotation: boolean) => - p.matrixTransform(withRotation ? toLocalRotatedM : toLocalM); - const toModel = (p: DOMPoint) => - p.matrixTransform(toLocalRotatedM.inverse()); - - const handleLocal = toLocal(new DOMPoint(startPt[0], startPt[1]), true); - const currPtLocal = toLocal( - new DOMPoint(startPt[0] + delta.dx, startPt[1] + delta.dy), - true - ); - - let scaleX = xSign - ? (xSign * (currPtLocal.x - handleLocal.x) + orig.w) / orig.w - : 1; - let scaleY = ySign - ? (ySign * (currPtLocal.y - handleLocal.y) + orig.h) / orig.h - : 1; - - if (lockRatio) { - const min = Math.min(Math.abs(scaleX), Math.abs(scaleY)); - scaleX = Math.sign(scaleX) * min; - scaleY = Math.sign(scaleY) * min; - } - - const scaleM = new DOMMatrix().scale(scaleX, scaleY); + const { toLocalM, toLocalRotatedM, toLocal, toModel } = transform; + const { scaleX, scaleY, scaleM } = scale; const [visualTopLeft, visualBottomRight] = [ new DOMPoint(orig.x, orig.y), @@ -282,45 +381,14 @@ export class ResizeController { } private resizeMulti( - originalBound: Bound, - originals: ElementInitialSnapshot[], + originals: ReadonlyIBound[], elements: GfxModel[], - startPt: IVec, - delta: { - dx: number; - dy: number; - }, - handleSign: { xSign: number; ySign: number }, + transform: ReturnType, + scale: { scaleX: number; scaleY: number; scaleM: DOMMatrix }, updateCallback: OptionResize['onResizeUpdate'] ) { - const { xSign, ySign } = handleSign; - const pivot = new DOMPoint( - originalBound.x + ((-xSign + 1) / 2) * originalBound.w, - originalBound.y + ((-ySign + 1) / 2) * originalBound.h - ); - const toLocalM = new DOMMatrix().translate(-pivot.x, -pivot.y); - - const toLocal = (p: DOMPoint) => p.matrixTransform(toLocalM); - - const handleLocal = toLocal(new DOMPoint(startPt[0], startPt[1])); - const currPtLocal = toLocal( - new DOMPoint(startPt[0] + delta.dx, startPt[1] + delta.dy) - ); - - let scaleX = xSign - ? (xSign * (currPtLocal.x - handleLocal.x) + originalBound.w) / - originalBound.w - : 1; - let scaleY = ySign - ? (ySign * (currPtLocal.y - handleLocal.y) + originalBound.h) / - originalBound.h - : 1; - - const min = Math.max(Math.abs(scaleX), Math.abs(scaleY)); - scaleX = Math.sign(scaleX) * min; - scaleY = Math.sign(scaleY) * min; - - const scaleM = new DOMMatrix().scale(scaleX, scaleY); + const { toLocalM } = transform; + const { scaleX, scaleY, scaleM } = scale; const data = elements.map((model, i) => { const orig = originals[i]; @@ -357,7 +425,7 @@ export class ResizeController { startRotate(option: RotateOption) { const { event, elements, onRotateUpdate } = option; - const originals: ElementInitialSnapshot[] = elements.map(el => ({ + const originals: ReadonlyIBound[] = elements.map(el => ({ x: el.x, y: el.y, w: el.w, @@ -429,7 +497,7 @@ export class ResizeController { } private rotateSingle(option: { - orig: ElementInitialSnapshot; + orig: ReadonlyIBound; model: GfxModel; startPt: IVec; currentPt: IVec; @@ -481,7 +549,7 @@ export class ResizeController { } private rotateMulti(option: { - origs: ElementInitialSnapshot[]; + origs: ReadonlyIBound[]; models: GfxModel[]; startPt: IVec; currentPt: IVec; @@ -567,23 +635,23 @@ export class ResizeController { private getHandleSign(handle: ResizeHandle) { switch (handle) { case 'top-left': - return { xSign: -1, ySign: -1 }; + return { x: -1, y: -1 }; case 'top': - return { xSign: 0, ySign: -1 }; + return { x: 0, y: -1 }; case 'top-right': - return { xSign: 1, ySign: -1 }; + return { x: 1, y: -1 }; case 'right': - return { xSign: 1, ySign: 0 }; + return { x: 1, y: 0 }; case 'bottom-right': - return { xSign: 1, ySign: 1 }; + return { x: 1, y: 1 }; case 'bottom': - return { xSign: 0, ySign: 1 }; + return { x: 0, y: 1 }; case 'bottom-left': - return { xSign: -1, ySign: 1 }; + return { x: -1, y: 1 }; case 'left': - return { xSign: -1, ySign: 0 }; + return { x: -1, y: 0 }; default: - return { xSign: 0, ySign: 0 }; + return { x: 0, y: 0 }; } } } diff --git a/blocksuite/framework/std/src/gfx/interactivity/types/resize.ts b/blocksuite/framework/std/src/gfx/interactivity/types/resize.ts index 465ba6ec40..f5f1b3d035 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/types/resize.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/types/resize.ts @@ -1,3 +1,5 @@ +import type { IBound, IPoint, IVec } from '@blocksuite/global/gfx'; + import type { GfxModel } from '../../model/model'; import type { ResizeHandle } from '../resize/manager'; @@ -8,6 +10,16 @@ export type ExtensionElementResizeContext = { export type ExtensionElementResizeStartContext = { elements: GfxModel[]; + /** + * The position of the handle in the browser coordinate space. + */ + handlePos: IVec; + + /** + * The sign (or normal vector) of the handle. + */ + handleSign: IPoint; + handle: ResizeHandle; }; @@ -16,15 +28,14 @@ export type ExtensionElementResizeEndContext = export type ExtensionElementResizeMoveContext = ExtensionElementResizeStartContext & { - dx: number; - dy: number; + scaleX: number; + scaleY: number; + + originalBound: IBound; + + currentHandlePos: IVec; lockRatio: boolean; - handleSign: { - xSign: number; - ySign: number; - }; - - suggest: (distance: { dx: number; dy: number }) => void; + suggest: (distance: { scaleX: number; scaleY: number }) => void; }; diff --git a/tests/blocksuite/e2e/edgeless/resizing.spec.ts b/tests/blocksuite/e2e/edgeless/resizing.spec.ts index 1f06682120..99f99f66f8 100644 --- a/tests/blocksuite/e2e/edgeless/resizing.spec.ts +++ b/tests/blocksuite/e2e/edgeless/resizing.spec.ts @@ -37,23 +37,16 @@ test.describe('resizing shapes and aspect ratio will be maintained', () => { await addBasicRectShapeElement( page, - { x: 210, y: 110 }, - { x: 310, y: 210 } + { x: 210, y: 210 }, + { x: 310, y: 310 } ); - await page.mouse.click(220, 120); - await assertEdgelessSelectedRect(page, [210, 110, 100, 100]); + await page.mouse.click(220, 220); - await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 }); - await assertEdgelessSelectedRect(page, [98, 98, 212, 112]); + await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 220 }); + await assertEdgelessSelectedRect(page, [98, 98, 212, 212]); await resizeElementByHandle(page, { x: 50, y: 50 }); - await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]); - - await page.mouse.move(160, 160); - await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]); - - await page.mouse.move(260, 160); - await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]); + await assertEdgelessSelectedRect(page, [148, 148, 162, 162]); }); test('negative adjustment', async ({ page }) => { @@ -73,23 +66,16 @@ test.describe('resizing shapes and aspect ratio will be maintained', () => { await addBasicRectShapeElement( page, - { x: 210, y: 110 }, - { x: 310, y: 210 } + { x: 210, y: 210 }, + { x: 310, y: 310 } ); - await page.mouse.click(220, 120); - await assertEdgelessSelectedRect(page, [210, 110, 100, 100]); + await page.mouse.click(220, 220); - await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 }); - await assertEdgelessSelectedRect(page, [98, 98, 212, 112]); + await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 220 }); + await assertEdgelessSelectedRect(page, [98, 98, 212, 212]); - await resizeElementByHandle(page, { x: 400, y: 300 }, 'top-left', 30); - await assertEdgelessSelectedRect(page, [310, 210, 356, 188]); - - await page.mouse.move(450, 300); - await assertEdgelessSelectedRect(page, [310, 210, 356, 188]); - - await page.mouse.move(320, 220); - await assertEdgelessSelectedRect(page, [310, 210, 356, 188]); + await resizeElementByHandle(page, { x: 50, y: 50 }, 'bottom-right', 30); + await assertEdgelessSelectedRect(page, [98, 98, 262, 262]); }); });