mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +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:
@@ -26,7 +26,7 @@ export class ToolOverlay extends Overlay {
|
||||
this.gfx.viewport.viewportUpdated.pipe(startWith(null)).subscribe(() => {
|
||||
// when viewport is updated, we should keep the overlay in the same position
|
||||
// to get last mouse position and convert it to model coordinates
|
||||
const pos = this.gfx.tool.lastMousePos$.value;
|
||||
const pos = this.gfx.tool.lastMouseViewPos$.value;
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(pos.x, pos.y);
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
@@ -26,9 +26,7 @@ export enum DefaultModeDragType {
|
||||
export class DefaultTool extends BaseTool {
|
||||
static override toolName: string = 'default';
|
||||
|
||||
private _accumulateDelta: IVec = [0, 0];
|
||||
|
||||
private _autoPanTimer: number | null = null;
|
||||
private _edgeScrollingTimer: number | null = null;
|
||||
|
||||
private readonly _clearDisposable = () => {
|
||||
if (this._disposables) {
|
||||
@@ -38,19 +36,17 @@ export class DefaultTool extends BaseTool {
|
||||
};
|
||||
|
||||
private readonly _clearSelectingState = () => {
|
||||
this._stopAutoPanning();
|
||||
this._stopEdgeScrolling();
|
||||
this._clearDisposable();
|
||||
};
|
||||
|
||||
private _disposables: DisposableGroup | null = null;
|
||||
|
||||
private _panViewport(delta: IVec) {
|
||||
this._accumulateDelta[0] += delta[0];
|
||||
this._accumulateDelta[1] += delta[1];
|
||||
private _scrollViewport(delta: IVec) {
|
||||
this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]);
|
||||
}
|
||||
|
||||
private _selectionRectTransition: null | {
|
||||
private _spaceTranslationRect: null | {
|
||||
w: number;
|
||||
h: number;
|
||||
startX: number;
|
||||
@@ -59,61 +55,43 @@ export class DefaultTool extends BaseTool {
|
||||
endY: number;
|
||||
} = null;
|
||||
|
||||
private readonly _startAutoPanning = (delta: IVec) => {
|
||||
this._panViewport(delta);
|
||||
this._updateSelectingState(delta);
|
||||
this._stopAutoPanning();
|
||||
private readonly _enableEdgeScrolling = (delta: IVec) => {
|
||||
this._stopEdgeScrolling();
|
||||
this._scrollViewport(delta);
|
||||
|
||||
this._autoPanTimer = window.setInterval(() => {
|
||||
this._panViewport(delta);
|
||||
this._updateSelectingState(delta);
|
||||
this._edgeScrollingTimer = window.setInterval(() => {
|
||||
this._scrollViewport(delta);
|
||||
}, 30);
|
||||
};
|
||||
|
||||
private readonly _stopAutoPanning = () => {
|
||||
if (this._autoPanTimer) {
|
||||
clearTimeout(this._autoPanTimer);
|
||||
this._autoPanTimer = null;
|
||||
private readonly _stopEdgeScrolling = () => {
|
||||
if (this._edgeScrollingTimer) {
|
||||
clearInterval(this._edgeScrollingTimer);
|
||||
this._edgeScrollingTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
private _toBeMoved: GfxModel[] = [];
|
||||
|
||||
private readonly _updateSelectingState = (delta: IVec = [0, 0]) => {
|
||||
private readonly _updateSelection = () => {
|
||||
const { gfx } = this;
|
||||
|
||||
if (gfx.keyboard.spaceKey$.peek() && this._selectionRectTransition) {
|
||||
/* Move the selection if space is pressed */
|
||||
const curDraggingViewArea = this.controller.draggingViewArea$.peek();
|
||||
const { w, h, startX, startY, endX, endY } =
|
||||
this._selectionRectTransition;
|
||||
const { endX: lastX, endY: lastY } = curDraggingViewArea;
|
||||
if (gfx.keyboard.spaceKey$.peek() && this._spaceTranslationRect) {
|
||||
const { w, h, startX, startY, endX, endY } = this._spaceTranslationRect;
|
||||
const { endX: lastX, endY: lastY } = this.controller.draggingArea$.peek();
|
||||
|
||||
const dx = lastX + delta[0] - endX + this._accumulateDelta[0];
|
||||
const dy = lastY + delta[1] - endY + this._accumulateDelta[1];
|
||||
const dx = lastX - endX;
|
||||
const dy = lastY - endY;
|
||||
|
||||
this.controller.draggingViewArea$.value = {
|
||||
...curDraggingViewArea,
|
||||
this.controller.draggingArea$.value = {
|
||||
x: Math.min(startX + dx, lastX),
|
||||
y: Math.min(startY + dy, lastY),
|
||||
w,
|
||||
h,
|
||||
startX: startX + dx,
|
||||
startY: startY + dy,
|
||||
};
|
||||
} else {
|
||||
const curDraggingArea = this.controller.draggingViewArea$.peek();
|
||||
const newStartX = curDraggingArea.startX - delta[0];
|
||||
const newStartY = curDraggingArea.startY - delta[1];
|
||||
|
||||
this.controller.draggingViewArea$.value = {
|
||||
...curDraggingArea,
|
||||
startX: newStartX,
|
||||
startY: newStartY,
|
||||
x: Math.min(newStartX, curDraggingArea.endX),
|
||||
y: Math.min(newStartY, curDraggingArea.endY),
|
||||
w: Math.abs(curDraggingArea.endX - newStartX),
|
||||
h: Math.abs(curDraggingArea.endY - newStartY),
|
||||
endX: endX + dx,
|
||||
endY: endY + dy,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,7 +152,7 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
|
||||
private _determineDragType(evt: PointerEventState): DefaultModeDragType {
|
||||
const { x, y } = this.controller.lastMouseModelPos$.peek();
|
||||
const { x, y } = this.controller.lastMousePos$.peek();
|
||||
if (this.selection.isInSelectedRect(x, y)) {
|
||||
if (this.selection.selectedElements.length === 1) {
|
||||
const currentHoveredElem = this._getElementInGroup(x, y);
|
||||
@@ -243,10 +221,9 @@ export class DefaultTool extends BaseTool {
|
||||
this.gfx.viewport.viewportUpdated.subscribe(() => {
|
||||
if (
|
||||
this.dragType === DefaultModeDragType.Selecting &&
|
||||
this.controller.dragging$.peek() &&
|
||||
!this._autoPanTimer
|
||||
this.controller.dragging$.peek()
|
||||
) {
|
||||
this._updateSelectingState();
|
||||
this._updateSelection();
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -280,9 +257,8 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
|
||||
override deactivate() {
|
||||
this._stopAutoPanning();
|
||||
this._stopEdgeScrolling();
|
||||
this._clearDisposable();
|
||||
this._accumulateDelta = [0, 0];
|
||||
}
|
||||
|
||||
override doubleClick(e: PointerEventState) {
|
||||
@@ -323,13 +299,12 @@ export class DefaultTool extends BaseTool {
|
||||
switch (this.dragType) {
|
||||
case DefaultModeDragType.Selecting: {
|
||||
// Record the last drag pointer position for auto panning and view port updating
|
||||
|
||||
this._updateSelectingState();
|
||||
this._updateSelection();
|
||||
const moveDelta = calPanDelta(viewport, e);
|
||||
if (moveDelta) {
|
||||
this._startAutoPanning(moveDelta);
|
||||
this._enableEdgeScrolling(moveDelta);
|
||||
} else {
|
||||
this._stopAutoPanning();
|
||||
this._stopEdgeScrolling();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -385,18 +360,11 @@ export class DefaultTool extends BaseTool {
|
||||
const pressed = this.gfx.keyboard.spaceKey$.value;
|
||||
|
||||
if (pressed) {
|
||||
const currentDraggingArea = this.controller.draggingViewArea$.peek();
|
||||
const currentDraggingArea = this.controller.draggingArea$.peek();
|
||||
|
||||
this._selectionRectTransition = {
|
||||
w: currentDraggingArea.w,
|
||||
h: currentDraggingArea.h,
|
||||
startX: currentDraggingArea.startX,
|
||||
startY: currentDraggingArea.startY,
|
||||
endX: currentDraggingArea.endX,
|
||||
endY: currentDraggingArea.endY,
|
||||
};
|
||||
this._spaceTranslationRect = currentDraggingArea;
|
||||
} else {
|
||||
this._selectionRectTransition = null;
|
||||
this._spaceTranslationRect = null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user