mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
fix: selection rect should reflect viewport change (#12355)
Fixes [BS-3349](https://linear.app/affine-design/issue/BS-3349/) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved edge scrolling during selection dragging for smoother and more responsive viewport navigation. - Dragging area and mouse position tracking now update reactively with viewport changes, ensuring more accurate selection and movement. - **Refactor** - Unified and clarified coordinate handling for dragging and mouse position, with clearer naming and separation between model and browser coordinates. - Simplified selection logic and removed unnecessary accumulated state for cleaner and more maintainable behavior. - Enhanced flexibility in coordinate conversion by allowing viewport transformations relative to arbitrary zoom and center. - Streamlined clipboard paste handling by simplifying mouse position extraction and adjusting attachment options. - **Bug Fixes** - Enhanced overlay and dragging area accuracy by updating position calculations and coordinate transformations. - Fixed paste operations to correctly handle mouse position without unnecessary coordinate conversions. - Corrected drag initiation positions in toolbar and shape dragging to align with viewport-relative coordinates. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -3,7 +3,7 @@ import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import type { IBound, IPoint } from '@blocksuite/global/gfx';
|
||||
import { computed, Signal } from '@preact/signals-core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Subject, type Subscription } from 'rxjs';
|
||||
|
||||
import type { PointerEventState } from '../../event/index.js';
|
||||
import type { GfxController } from '../controller.js';
|
||||
@@ -75,6 +75,13 @@ export interface ToolEventTarget {
|
||||
): void;
|
||||
}
|
||||
|
||||
type AreaBound = IBound & {
|
||||
startX: number;
|
||||
startY: number;
|
||||
endX: number;
|
||||
endY: number;
|
||||
};
|
||||
|
||||
export class ToolController extends GfxExtension {
|
||||
static override key = 'ToolController';
|
||||
|
||||
@@ -91,8 +98,39 @@ export class ToolController extends GfxExtension {
|
||||
readonly dragging$ = new Signal<boolean>(false);
|
||||
|
||||
/**
|
||||
* The area that is being dragged.
|
||||
* The coordinates are in browser space.
|
||||
* The dragging area in browser coordinates space.
|
||||
*
|
||||
* This is similar to `draggingViewArea$`, but if the viewport is changed during dragging,
|
||||
* it will be reflected in this area.
|
||||
*/
|
||||
readonly draggingViewportArea$ = computed(() => {
|
||||
const compute = (modelArea: AreaBound) => {
|
||||
const [viewStartX, viewStartY] = this.gfx.viewport.toViewCoord(
|
||||
modelArea.x,
|
||||
modelArea.y
|
||||
);
|
||||
const [viewEndX, viewEndY] = this.gfx.viewport.toViewCoord(
|
||||
modelArea.x + modelArea.w,
|
||||
modelArea.y + modelArea.h
|
||||
);
|
||||
|
||||
return {
|
||||
startX: viewStartX,
|
||||
startY: viewStartY,
|
||||
endX: viewEndX,
|
||||
endY: viewEndY,
|
||||
x: Math.min(viewStartX, viewEndX),
|
||||
y: Math.min(viewStartY, viewEndY),
|
||||
w: Math.abs(viewStartX - viewEndX),
|
||||
h: Math.abs(viewStartY - viewEndY),
|
||||
};
|
||||
};
|
||||
|
||||
return compute(this.draggingArea$.value);
|
||||
});
|
||||
|
||||
/**
|
||||
* The dragging area in browser coordinates space.
|
||||
*/
|
||||
readonly draggingViewArea$ = new Signal<
|
||||
IBound & {
|
||||
@@ -113,18 +151,20 @@ export class ToolController extends GfxExtension {
|
||||
});
|
||||
|
||||
/**
|
||||
* The last mouse move position
|
||||
* The coordinates are in browser space
|
||||
* The last mouse move position in browser coordinates space.
|
||||
*/
|
||||
readonly lastMousePos$ = new Signal<IPoint>({
|
||||
readonly lastMouseViewPos$ = new Signal<IPoint>({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
readonly lastMouseModelPos$ = computed(() => {
|
||||
/**
|
||||
* The last mouse position in model coordinates space.
|
||||
*/
|
||||
readonly lastMousePos$ = computed(() => {
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
||||
this.lastMousePos$.value.x,
|
||||
this.lastMousePos$.value.y
|
||||
this.lastMouseViewPos$.value.x,
|
||||
this.lastMouseViewPos$.value.y
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -166,38 +206,23 @@ export class ToolController extends GfxExtension {
|
||||
* The area that is being dragged.
|
||||
* The coordinates are in model space.
|
||||
*/
|
||||
get draggingArea$() {
|
||||
const compute = (peek: boolean) => {
|
||||
const area = peek
|
||||
? this.draggingViewArea$.peek()
|
||||
: this.draggingViewArea$.value;
|
||||
const [startX, startY] = this.gfx.viewport.toModelCoord(
|
||||
area.startX,
|
||||
area.startY
|
||||
);
|
||||
const [endX, endY] = this.gfx.viewport.toModelCoord(area.endX, area.endY);
|
||||
|
||||
return {
|
||||
x: Math.min(startX, endX),
|
||||
y: Math.min(startY, endY),
|
||||
w: Math.abs(endX - startX),
|
||||
h: Math.abs(endY - startY),
|
||||
startX,
|
||||
startY,
|
||||
endX,
|
||||
endY,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
value() {
|
||||
return compute(false);
|
||||
},
|
||||
peek() {
|
||||
return compute(true);
|
||||
},
|
||||
};
|
||||
}
|
||||
readonly draggingArea$ = new Signal<
|
||||
IBound & {
|
||||
startX: number;
|
||||
startY: number;
|
||||
endX: number;
|
||||
endY: number;
|
||||
}
|
||||
>({
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
endX: 0,
|
||||
endY: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0,
|
||||
h: 0,
|
||||
});
|
||||
|
||||
static override extendGfx(gfx: GfxController) {
|
||||
Object.defineProperty(gfx, 'tool', {
|
||||
@@ -299,6 +324,7 @@ export class ToolController extends GfxExtension {
|
||||
let dragContext: {
|
||||
tool: BaseTool;
|
||||
} | null = null;
|
||||
let viewportSub: Subscription | null = null;
|
||||
|
||||
this._disposableGroup.add(
|
||||
this.std.event.add('dragStart', ctx => {
|
||||
@@ -315,6 +341,8 @@ export class ToolController extends GfxExtension {
|
||||
evt.raw.preventDefault();
|
||||
}
|
||||
|
||||
const [modelX, modelY] = this.gfx.viewport.toModelCoord(evt.x, evt.y);
|
||||
|
||||
this.dragging$.value = true;
|
||||
this.draggingViewArea$.value = {
|
||||
startX: evt.x,
|
||||
@@ -326,11 +354,42 @@ export class ToolController extends GfxExtension {
|
||||
w: 0,
|
||||
h: 0,
|
||||
};
|
||||
this.lastMousePos$.value = {
|
||||
this.draggingArea$.value = {
|
||||
startX: modelX,
|
||||
startY: modelY,
|
||||
endX: modelX,
|
||||
endY: modelY,
|
||||
x: modelX,
|
||||
y: modelY,
|
||||
w: 0,
|
||||
h: 0,
|
||||
};
|
||||
this.lastMouseViewPos$.value = {
|
||||
x: evt.x,
|
||||
y: evt.y,
|
||||
};
|
||||
|
||||
viewportSub?.unsubscribe();
|
||||
viewportSub = this.gfx.viewport.viewportUpdated.subscribe(() => {
|
||||
const lastPost = this.lastMouseViewPos$.peek();
|
||||
const [modelX, modelY] = this.gfx.viewport.toModelCoord(
|
||||
lastPost.x,
|
||||
lastPost.y
|
||||
);
|
||||
|
||||
const original = this.draggingArea$.peek();
|
||||
|
||||
this.draggingArea$.value = {
|
||||
...this.draggingArea$.peek(),
|
||||
x: Math.min(modelX, original.startX),
|
||||
y: Math.min(modelY, original.startY),
|
||||
w: Math.abs(modelX - original.startX),
|
||||
h: Math.abs(modelY - original.startY),
|
||||
endX: modelX,
|
||||
endY: modelY,
|
||||
};
|
||||
});
|
||||
|
||||
// this means the dragEnd event is not even fired
|
||||
// so we need to manually call the dragEnd method
|
||||
if (dragContext?.tool) {
|
||||
@@ -361,6 +420,7 @@ export class ToolController extends GfxExtension {
|
||||
originX: this.draggingViewArea$.peek().startX,
|
||||
originY: this.draggingViewArea$.peek().startY,
|
||||
};
|
||||
const [modelX, modelY] = this.gfx.viewport.toModelCoord(evt.x, evt.y);
|
||||
|
||||
this.draggingViewArea$.value = {
|
||||
...this.draggingViewArea$.peek(),
|
||||
@@ -372,7 +432,17 @@ export class ToolController extends GfxExtension {
|
||||
endY: evt.y,
|
||||
};
|
||||
|
||||
this.lastMousePos$.value = {
|
||||
this.draggingArea$.value = {
|
||||
...this.draggingArea$.peek(),
|
||||
w: Math.abs(modelX - draggingStart.x),
|
||||
h: Math.abs(modelY - draggingStart.y),
|
||||
x: Math.min(modelX, draggingStart.x),
|
||||
y: Math.min(modelY, draggingStart.y),
|
||||
endX: modelX,
|
||||
endY: modelY,
|
||||
};
|
||||
|
||||
this.lastMouseViewPos$.value = {
|
||||
x: evt.x,
|
||||
y: evt.y,
|
||||
};
|
||||
@@ -399,6 +469,8 @@ export class ToolController extends GfxExtension {
|
||||
dragContext.tool.dragEnd(evt);
|
||||
}
|
||||
|
||||
viewportSub?.unsubscribe();
|
||||
viewportSub = null;
|
||||
dragContext = null;
|
||||
this.draggingViewArea$.value = {
|
||||
x: 0,
|
||||
@@ -410,6 +482,16 @@ export class ToolController extends GfxExtension {
|
||||
w: 0,
|
||||
h: 0,
|
||||
};
|
||||
this.draggingArea$.value = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
endX: 0,
|
||||
endY: 0,
|
||||
w: 0,
|
||||
h: 0,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
@@ -417,7 +499,7 @@ export class ToolController extends GfxExtension {
|
||||
this.std.event.add('pointerMove', ctx => {
|
||||
const evt = ctx.get('pointerState');
|
||||
|
||||
this.lastMousePos$.value = {
|
||||
this.lastMouseViewPos$.value = {
|
||||
x: evt.x,
|
||||
y: evt.y,
|
||||
};
|
||||
|
||||
@@ -591,8 +591,20 @@ export class Viewport {
|
||||
return new Bound(x, y, w / this.zoom, h / this.zoom);
|
||||
}
|
||||
|
||||
toModelCoord(viewX: number, viewY: number): IVec {
|
||||
const { viewportX, viewportY, zoom, viewScale } = this;
|
||||
toModelCoord(
|
||||
viewX: number,
|
||||
viewY: number,
|
||||
zoom = this.zoom,
|
||||
center?: IPoint
|
||||
): IVec {
|
||||
const { viewScale } = this;
|
||||
const viewportX = center
|
||||
? center.x - this.width / 2 / zoom
|
||||
: this.viewportX;
|
||||
const viewportY = center
|
||||
? center.y - this.height / 2 / zoom
|
||||
: this.viewportY;
|
||||
|
||||
return [
|
||||
viewportX + viewX / zoom / viewScale,
|
||||
viewportY + viewY / zoom / viewScale,
|
||||
|
||||
Reference in New Issue
Block a user