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:
@@ -209,9 +209,10 @@ export class EdgelessClipboardController extends PageClipboard {
|
|||||||
await addImages(this.std, imageFiles, {
|
await addImages(this.std, imageFiles, {
|
||||||
point,
|
point,
|
||||||
maxWidth: MAX_IMAGE_WIDTH,
|
maxWidth: MAX_IMAGE_WIDTH,
|
||||||
|
shouldTransformPoint: false,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await addAttachments(this.std, [...files], point);
|
await addAttachments(this.std, [...files], point, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
|
this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
|
||||||
@@ -227,11 +228,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
|||||||
|
|
||||||
if (isUrlInClipboard(data)) {
|
if (isUrlInClipboard(data)) {
|
||||||
const url = data.getData('text/plain');
|
const url = data.getData('text/plain');
|
||||||
const lastMousePos = this.toolManager.lastMousePos$.peek();
|
const { x, y } = this.toolManager.lastMousePos$.peek();
|
||||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
|
||||||
lastMousePos.x,
|
|
||||||
lastMousePos.y
|
|
||||||
);
|
|
||||||
|
|
||||||
// try to interpret url as affine doc url
|
// try to interpret url as affine doc url
|
||||||
const parseDocUrlService = this.std.getOptional(ParseDocUrlProvider);
|
const parseDocUrlService = this.std.getOptional(ParseDocUrlProvider);
|
||||||
@@ -562,11 +559,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _pasteTextContentAsNote(content: BlockSnapshot[] | string) {
|
private async _pasteTextContentAsNote(content: BlockSnapshot[] | string) {
|
||||||
const lastMousePos = this.toolManager.lastMousePos$.peek();
|
const { x, y } = this.toolManager.lastMousePos$.peek();
|
||||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
|
||||||
lastMousePos.x,
|
|
||||||
lastMousePos.y
|
|
||||||
);
|
|
||||||
|
|
||||||
const noteProps = {
|
const noteProps = {
|
||||||
xywh: new Bound(
|
xywh: new Bound(
|
||||||
|
|||||||
@@ -52,9 +52,7 @@ export const createElementsFromClipboardDataCommand: Command<Input, Output> = (
|
|||||||
let oldCommonBound, pasteX, pasteY;
|
let oldCommonBound, pasteX, pasteY;
|
||||||
{
|
{
|
||||||
const lastMousePos = toolManager.lastMousePos$.peek();
|
const lastMousePos = toolManager.lastMousePos$.peek();
|
||||||
pasteCenter =
|
pasteCenter = pasteCenter ?? [lastMousePos.x, lastMousePos.y];
|
||||||
pasteCenter ??
|
|
||||||
gfx.viewport.toModelCoord(lastMousePos.x, lastMousePos.y);
|
|
||||||
const [modelX, modelY] = pasteCenter;
|
const [modelX, modelY] = pasteCenter;
|
||||||
oldCommonBound = edgelessElementsBoundFromRawData(elementsRawData);
|
oldCommonBound = edgelessElementsBoundFromRawData(elementsRawData);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class ToolOverlay extends Overlay {
|
|||||||
this.gfx.viewport.viewportUpdated.pipe(startWith(null)).subscribe(() => {
|
this.gfx.viewport.viewportUpdated.pipe(startWith(null)).subscribe(() => {
|
||||||
// when viewport is updated, we should keep the overlay in the same position
|
// when viewport is updated, we should keep the overlay in the same position
|
||||||
// to get last mouse position and convert it to model coordinates
|
// 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);
|
const [x, y] = this.gfx.viewport.toModelCoord(pos.x, pos.y);
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ export enum DefaultModeDragType {
|
|||||||
export class DefaultTool extends BaseTool {
|
export class DefaultTool extends BaseTool {
|
||||||
static override toolName: string = 'default';
|
static override toolName: string = 'default';
|
||||||
|
|
||||||
private _accumulateDelta: IVec = [0, 0];
|
private _edgeScrollingTimer: number | null = null;
|
||||||
|
|
||||||
private _autoPanTimer: number | null = null;
|
|
||||||
|
|
||||||
private readonly _clearDisposable = () => {
|
private readonly _clearDisposable = () => {
|
||||||
if (this._disposables) {
|
if (this._disposables) {
|
||||||
@@ -38,19 +36,17 @@ export class DefaultTool extends BaseTool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private readonly _clearSelectingState = () => {
|
private readonly _clearSelectingState = () => {
|
||||||
this._stopAutoPanning();
|
this._stopEdgeScrolling();
|
||||||
this._clearDisposable();
|
this._clearDisposable();
|
||||||
};
|
};
|
||||||
|
|
||||||
private _disposables: DisposableGroup | null = null;
|
private _disposables: DisposableGroup | null = null;
|
||||||
|
|
||||||
private _panViewport(delta: IVec) {
|
private _scrollViewport(delta: IVec) {
|
||||||
this._accumulateDelta[0] += delta[0];
|
|
||||||
this._accumulateDelta[1] += delta[1];
|
|
||||||
this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]);
|
this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _selectionRectTransition: null | {
|
private _spaceTranslationRect: null | {
|
||||||
w: number;
|
w: number;
|
||||||
h: number;
|
h: number;
|
||||||
startX: number;
|
startX: number;
|
||||||
@@ -59,61 +55,43 @@ export class DefaultTool extends BaseTool {
|
|||||||
endY: number;
|
endY: number;
|
||||||
} = null;
|
} = null;
|
||||||
|
|
||||||
private readonly _startAutoPanning = (delta: IVec) => {
|
private readonly _enableEdgeScrolling = (delta: IVec) => {
|
||||||
this._panViewport(delta);
|
this._stopEdgeScrolling();
|
||||||
this._updateSelectingState(delta);
|
this._scrollViewport(delta);
|
||||||
this._stopAutoPanning();
|
|
||||||
|
|
||||||
this._autoPanTimer = window.setInterval(() => {
|
this._edgeScrollingTimer = window.setInterval(() => {
|
||||||
this._panViewport(delta);
|
this._scrollViewport(delta);
|
||||||
this._updateSelectingState(delta);
|
|
||||||
}, 30);
|
}, 30);
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _stopAutoPanning = () => {
|
private readonly _stopEdgeScrolling = () => {
|
||||||
if (this._autoPanTimer) {
|
if (this._edgeScrollingTimer) {
|
||||||
clearTimeout(this._autoPanTimer);
|
clearInterval(this._edgeScrollingTimer);
|
||||||
this._autoPanTimer = null;
|
this._edgeScrollingTimer = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private _toBeMoved: GfxModel[] = [];
|
private _toBeMoved: GfxModel[] = [];
|
||||||
|
|
||||||
private readonly _updateSelectingState = (delta: IVec = [0, 0]) => {
|
private readonly _updateSelection = () => {
|
||||||
const { gfx } = this;
|
const { gfx } = this;
|
||||||
|
|
||||||
if (gfx.keyboard.spaceKey$.peek() && this._selectionRectTransition) {
|
if (gfx.keyboard.spaceKey$.peek() && this._spaceTranslationRect) {
|
||||||
/* Move the selection if space is pressed */
|
const { w, h, startX, startY, endX, endY } = this._spaceTranslationRect;
|
||||||
const curDraggingViewArea = this.controller.draggingViewArea$.peek();
|
const { endX: lastX, endY: lastY } = this.controller.draggingArea$.peek();
|
||||||
const { w, h, startX, startY, endX, endY } =
|
|
||||||
this._selectionRectTransition;
|
|
||||||
const { endX: lastX, endY: lastY } = curDraggingViewArea;
|
|
||||||
|
|
||||||
const dx = lastX + delta[0] - endX + this._accumulateDelta[0];
|
const dx = lastX - endX;
|
||||||
const dy = lastY + delta[1] - endY + this._accumulateDelta[1];
|
const dy = lastY - endY;
|
||||||
|
|
||||||
this.controller.draggingViewArea$.value = {
|
this.controller.draggingArea$.value = {
|
||||||
...curDraggingViewArea,
|
|
||||||
x: Math.min(startX + dx, lastX),
|
x: Math.min(startX + dx, lastX),
|
||||||
y: Math.min(startY + dy, lastY),
|
y: Math.min(startY + dy, lastY),
|
||||||
w,
|
w,
|
||||||
h,
|
h,
|
||||||
startX: startX + dx,
|
startX: startX + dx,
|
||||||
startY: startY + dy,
|
startY: startY + dy,
|
||||||
};
|
endX: endX + dx,
|
||||||
} else {
|
endY: endY + dy,
|
||||||
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),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +152,7 @@ export class DefaultTool extends BaseTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _determineDragType(evt: PointerEventState): DefaultModeDragType {
|
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.isInSelectedRect(x, y)) {
|
||||||
if (this.selection.selectedElements.length === 1) {
|
if (this.selection.selectedElements.length === 1) {
|
||||||
const currentHoveredElem = this._getElementInGroup(x, y);
|
const currentHoveredElem = this._getElementInGroup(x, y);
|
||||||
@@ -243,10 +221,9 @@ export class DefaultTool extends BaseTool {
|
|||||||
this.gfx.viewport.viewportUpdated.subscribe(() => {
|
this.gfx.viewport.viewportUpdated.subscribe(() => {
|
||||||
if (
|
if (
|
||||||
this.dragType === DefaultModeDragType.Selecting &&
|
this.dragType === DefaultModeDragType.Selecting &&
|
||||||
this.controller.dragging$.peek() &&
|
this.controller.dragging$.peek()
|
||||||
!this._autoPanTimer
|
|
||||||
) {
|
) {
|
||||||
this._updateSelectingState();
|
this._updateSelection();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -280,9 +257,8 @@ export class DefaultTool extends BaseTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override deactivate() {
|
override deactivate() {
|
||||||
this._stopAutoPanning();
|
this._stopEdgeScrolling();
|
||||||
this._clearDisposable();
|
this._clearDisposable();
|
||||||
this._accumulateDelta = [0, 0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override doubleClick(e: PointerEventState) {
|
override doubleClick(e: PointerEventState) {
|
||||||
@@ -323,13 +299,12 @@ export class DefaultTool extends BaseTool {
|
|||||||
switch (this.dragType) {
|
switch (this.dragType) {
|
||||||
case DefaultModeDragType.Selecting: {
|
case DefaultModeDragType.Selecting: {
|
||||||
// Record the last drag pointer position for auto panning and view port updating
|
// Record the last drag pointer position for auto panning and view port updating
|
||||||
|
this._updateSelection();
|
||||||
this._updateSelectingState();
|
|
||||||
const moveDelta = calPanDelta(viewport, e);
|
const moveDelta = calPanDelta(viewport, e);
|
||||||
if (moveDelta) {
|
if (moveDelta) {
|
||||||
this._startAutoPanning(moveDelta);
|
this._enableEdgeScrolling(moveDelta);
|
||||||
} else {
|
} else {
|
||||||
this._stopAutoPanning();
|
this._stopEdgeScrolling();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -385,18 +360,11 @@ export class DefaultTool extends BaseTool {
|
|||||||
const pressed = this.gfx.keyboard.spaceKey$.value;
|
const pressed = this.gfx.keyboard.spaceKey$.value;
|
||||||
|
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
const currentDraggingArea = this.controller.draggingViewArea$.peek();
|
const currentDraggingArea = this.controller.draggingArea$.peek();
|
||||||
|
|
||||||
this._selectionRectTransition = {
|
this._spaceTranslationRect = currentDraggingArea;
|
||||||
w: currentDraggingArea.w,
|
|
||||||
h: currentDraggingArea.h,
|
|
||||||
startX: currentDraggingArea.startX,
|
|
||||||
startY: currentDraggingArea.startY,
|
|
||||||
endX: currentDraggingArea.endX,
|
|
||||||
endY: currentDraggingArea.endY,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
this._selectionRectTransition = null;
|
this._spaceTranslationRect = null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin(
|
|||||||
}
|
}
|
||||||
this.setEdgelessTool(EmptyTool);
|
this.setEdgelessTool(EmptyTool);
|
||||||
const icon = this.mindmapElement;
|
const icon = this.mindmapElement;
|
||||||
const { x, y } = gfx.tool.lastMousePos$.peek();
|
const { x, y } = gfx.tool.lastMouseViewPos$.peek();
|
||||||
const { viewport } = this.edgeless.std.get(ViewportElementProvider);
|
const { viewport } = this.edgeless.std.get(ViewportElementProvider);
|
||||||
const { left, top } = viewport;
|
const { left, top } = viewport;
|
||||||
const clientPos = { x: x + left, y: y + top };
|
const clientPos = { x: x + left, y: y + top };
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
|
|||||||
console.error('Edgeless toolbar Shape element not found');
|
console.error('Edgeless toolbar Shape element not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { x, y } = this.gfx.tool.lastMousePos$.peek();
|
const { x, y } = this.gfx.tool.lastMouseViewPos$.peek();
|
||||||
const { viewport } = this.edgeless.std.get(ViewportElementProvider);
|
const { viewport } = this.edgeless.std.get(ViewportElementProvider);
|
||||||
const { left, top } = viewport;
|
const { left, top } = viewport;
|
||||||
const clientPos = { x: x + left, y: y + top };
|
const clientPos = { x: x + left, y: y + top };
|
||||||
|
|||||||
@@ -117,24 +117,24 @@ export class ShapeTool extends BaseTool<ShapeToolOption> {
|
|||||||
|
|
||||||
if (spacePressed && this._spacePressedCtx) {
|
if (spacePressed && this._spacePressedCtx) {
|
||||||
const {
|
const {
|
||||||
startX,
|
|
||||||
startY,
|
|
||||||
w,
|
w,
|
||||||
h,
|
h,
|
||||||
|
startX,
|
||||||
|
startY,
|
||||||
endX: pressedX,
|
endX: pressedX,
|
||||||
endY: pressedY,
|
endY: pressedY,
|
||||||
} = this._spacePressedCtx.draggingArea;
|
} = this._spacePressedCtx.draggingArea;
|
||||||
const curDraggingArea = controller.draggingViewArea$.peek();
|
const { endX: lastX, endY: lastY } = controller.draggingArea$.peek();
|
||||||
const { endX: lastX, endY: lastY } = curDraggingArea;
|
|
||||||
const dx = lastX - pressedX;
|
const dx = lastX - pressedX;
|
||||||
const dy = lastY - pressedY;
|
const dy = lastY - pressedY;
|
||||||
|
|
||||||
this.controller.draggingViewArea$.value = {
|
this.controller.draggingArea$.value = {
|
||||||
...curDraggingArea,
|
|
||||||
x: Math.min(startX + dx, lastX),
|
x: Math.min(startX + dx, lastX),
|
||||||
y: Math.min(startY + dy, lastY),
|
y: Math.min(startY + dy, lastY),
|
||||||
w,
|
w,
|
||||||
h,
|
h,
|
||||||
|
endX: endX + dx,
|
||||||
|
endY: endY + dy,
|
||||||
startX: startX + dx,
|
startX: startX + dx,
|
||||||
startY: startY + dy,
|
startY: startY + dy,
|
||||||
};
|
};
|
||||||
@@ -306,7 +306,7 @@ export class ShapeTool extends BaseTool<ShapeToolOption> {
|
|||||||
|
|
||||||
if (spacePressed && this._draggingElementId) {
|
if (spacePressed && this._draggingElementId) {
|
||||||
this._spacePressedCtx = {
|
this._spacePressedCtx = {
|
||||||
draggingArea: this.controller.draggingViewArea$.peek(),
|
draggingArea: this.controller.draggingArea$.peek(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class EdgelessDraggingAreaRectWidget extends WidgetComponent<RootBlockMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const rect = this.gfx.tool.draggingViewArea$.value;
|
const rect = this.gfx.tool.draggingViewportArea$.value;
|
||||||
const tool = this.gfx.tool.currentTool$.value;
|
const tool = this.gfx.tool.currentTool$.value;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { DisposableGroup } from '@blocksuite/global/disposable';
|
|||||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||||
import type { IBound, IPoint } from '@blocksuite/global/gfx';
|
import type { IBound, IPoint } from '@blocksuite/global/gfx';
|
||||||
import { computed, Signal } from '@preact/signals-core';
|
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 { PointerEventState } from '../../event/index.js';
|
||||||
import type { GfxController } from '../controller.js';
|
import type { GfxController } from '../controller.js';
|
||||||
@@ -75,6 +75,13 @@ export interface ToolEventTarget {
|
|||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AreaBound = IBound & {
|
||||||
|
startX: number;
|
||||||
|
startY: number;
|
||||||
|
endX: number;
|
||||||
|
endY: number;
|
||||||
|
};
|
||||||
|
|
||||||
export class ToolController extends GfxExtension {
|
export class ToolController extends GfxExtension {
|
||||||
static override key = 'ToolController';
|
static override key = 'ToolController';
|
||||||
|
|
||||||
@@ -91,8 +98,39 @@ export class ToolController extends GfxExtension {
|
|||||||
readonly dragging$ = new Signal<boolean>(false);
|
readonly dragging$ = new Signal<boolean>(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The area that is being dragged.
|
* The dragging area in browser coordinates space.
|
||||||
* The coordinates are in browser 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<
|
readonly draggingViewArea$ = new Signal<
|
||||||
IBound & {
|
IBound & {
|
||||||
@@ -113,18 +151,20 @@ export class ToolController extends GfxExtension {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The last mouse move position
|
* The last mouse move position in browser coordinates space.
|
||||||
* The coordinates are in browser space
|
|
||||||
*/
|
*/
|
||||||
readonly lastMousePos$ = new Signal<IPoint>({
|
readonly lastMouseViewPos$ = new Signal<IPoint>({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
readonly lastMouseModelPos$ = computed(() => {
|
/**
|
||||||
|
* The last mouse position in model coordinates space.
|
||||||
|
*/
|
||||||
|
readonly lastMousePos$ = computed(() => {
|
||||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
const [x, y] = this.gfx.viewport.toModelCoord(
|
||||||
this.lastMousePos$.value.x,
|
this.lastMouseViewPos$.value.x,
|
||||||
this.lastMousePos$.value.y
|
this.lastMouseViewPos$.value.y
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -166,38 +206,23 @@ export class ToolController extends GfxExtension {
|
|||||||
* The area that is being dragged.
|
* The area that is being dragged.
|
||||||
* The coordinates are in model space.
|
* The coordinates are in model space.
|
||||||
*/
|
*/
|
||||||
get draggingArea$() {
|
readonly draggingArea$ = new Signal<
|
||||||
const compute = (peek: boolean) => {
|
IBound & {
|
||||||
const area = peek
|
startX: number;
|
||||||
? this.draggingViewArea$.peek()
|
startY: number;
|
||||||
: this.draggingViewArea$.value;
|
endX: number;
|
||||||
const [startX, startY] = this.gfx.viewport.toModelCoord(
|
endY: number;
|
||||||
area.startX,
|
}
|
||||||
area.startY
|
>({
|
||||||
);
|
startX: 0,
|
||||||
const [endX, endY] = this.gfx.viewport.toModelCoord(area.endX, area.endY);
|
startY: 0,
|
||||||
|
endX: 0,
|
||||||
return {
|
endY: 0,
|
||||||
x: Math.min(startX, endX),
|
x: 0,
|
||||||
y: Math.min(startY, endY),
|
y: 0,
|
||||||
w: Math.abs(endX - startX),
|
w: 0,
|
||||||
h: Math.abs(endY - startY),
|
h: 0,
|
||||||
startX,
|
});
|
||||||
startY,
|
|
||||||
endX,
|
|
||||||
endY,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
value() {
|
|
||||||
return compute(false);
|
|
||||||
},
|
|
||||||
peek() {
|
|
||||||
return compute(true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static override extendGfx(gfx: GfxController) {
|
static override extendGfx(gfx: GfxController) {
|
||||||
Object.defineProperty(gfx, 'tool', {
|
Object.defineProperty(gfx, 'tool', {
|
||||||
@@ -299,6 +324,7 @@ export class ToolController extends GfxExtension {
|
|||||||
let dragContext: {
|
let dragContext: {
|
||||||
tool: BaseTool;
|
tool: BaseTool;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
let viewportSub: Subscription | null = null;
|
||||||
|
|
||||||
this._disposableGroup.add(
|
this._disposableGroup.add(
|
||||||
this.std.event.add('dragStart', ctx => {
|
this.std.event.add('dragStart', ctx => {
|
||||||
@@ -315,6 +341,8 @@ export class ToolController extends GfxExtension {
|
|||||||
evt.raw.preventDefault();
|
evt.raw.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [modelX, modelY] = this.gfx.viewport.toModelCoord(evt.x, evt.y);
|
||||||
|
|
||||||
this.dragging$.value = true;
|
this.dragging$.value = true;
|
||||||
this.draggingViewArea$.value = {
|
this.draggingViewArea$.value = {
|
||||||
startX: evt.x,
|
startX: evt.x,
|
||||||
@@ -326,11 +354,42 @@ export class ToolController extends GfxExtension {
|
|||||||
w: 0,
|
w: 0,
|
||||||
h: 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,
|
x: evt.x,
|
||||||
y: evt.y,
|
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
|
// this means the dragEnd event is not even fired
|
||||||
// so we need to manually call the dragEnd method
|
// so we need to manually call the dragEnd method
|
||||||
if (dragContext?.tool) {
|
if (dragContext?.tool) {
|
||||||
@@ -361,6 +420,7 @@ export class ToolController extends GfxExtension {
|
|||||||
originX: this.draggingViewArea$.peek().startX,
|
originX: this.draggingViewArea$.peek().startX,
|
||||||
originY: this.draggingViewArea$.peek().startY,
|
originY: this.draggingViewArea$.peek().startY,
|
||||||
};
|
};
|
||||||
|
const [modelX, modelY] = this.gfx.viewport.toModelCoord(evt.x, evt.y);
|
||||||
|
|
||||||
this.draggingViewArea$.value = {
|
this.draggingViewArea$.value = {
|
||||||
...this.draggingViewArea$.peek(),
|
...this.draggingViewArea$.peek(),
|
||||||
@@ -372,7 +432,17 @@ export class ToolController extends GfxExtension {
|
|||||||
endY: evt.y,
|
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,
|
x: evt.x,
|
||||||
y: evt.y,
|
y: evt.y,
|
||||||
};
|
};
|
||||||
@@ -399,6 +469,8 @@ export class ToolController extends GfxExtension {
|
|||||||
dragContext.tool.dragEnd(evt);
|
dragContext.tool.dragEnd(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewportSub?.unsubscribe();
|
||||||
|
viewportSub = null;
|
||||||
dragContext = null;
|
dragContext = null;
|
||||||
this.draggingViewArea$.value = {
|
this.draggingViewArea$.value = {
|
||||||
x: 0,
|
x: 0,
|
||||||
@@ -410,6 +482,16 @@ export class ToolController extends GfxExtension {
|
|||||||
w: 0,
|
w: 0,
|
||||||
h: 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 => {
|
this.std.event.add('pointerMove', ctx => {
|
||||||
const evt = ctx.get('pointerState');
|
const evt = ctx.get('pointerState');
|
||||||
|
|
||||||
this.lastMousePos$.value = {
|
this.lastMouseViewPos$.value = {
|
||||||
x: evt.x,
|
x: evt.x,
|
||||||
y: evt.y,
|
y: evt.y,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -591,8 +591,20 @@ export class Viewport {
|
|||||||
return new Bound(x, y, w / this.zoom, h / this.zoom);
|
return new Bound(x, y, w / this.zoom, h / this.zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
toModelCoord(viewX: number, viewY: number): IVec {
|
toModelCoord(
|
||||||
const { viewportX, viewportY, zoom, viewScale } = this;
|
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 [
|
return [
|
||||||
viewportX + viewX / zoom / viewScale,
|
viewportX + viewX / zoom / viewScale,
|
||||||
viewportY + viewY / zoom / viewScale,
|
viewportY + viewY / zoom / viewScale,
|
||||||
|
|||||||
Reference in New Issue
Block a user