fix: snap line offset & check alignment candidates in real time (#10605)
This commit is contained in:
@@ -49,7 +49,6 @@ import clamp from 'lodash-es/clamp';
|
|||||||
|
|
||||||
import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js';
|
import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js';
|
||||||
import { prepareCloneData } from '../utils/clone-utils.js';
|
import { prepareCloneData } from '../utils/clone-utils.js';
|
||||||
import { isSingleMindMapNode } from '../utils/mindmap.js';
|
|
||||||
import { calPanDelta } from '../utils/panning-utils.js';
|
import { calPanDelta } from '../utils/panning-utils.js';
|
||||||
import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js';
|
import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js';
|
||||||
import type { SnapManager } from '../utils/snap-manager.js';
|
import type { SnapManager } from '../utils/snap-manager.js';
|
||||||
@@ -561,22 +560,24 @@ export class DefaultTool extends BaseTool {
|
|||||||
) {
|
) {
|
||||||
this.dragType = dragType;
|
this.dragType = dragType;
|
||||||
|
|
||||||
|
const mindmaps: MindmapElementModel[] = this._toBeMoved.reduce(
|
||||||
|
(pre, elem) => {
|
||||||
if (
|
if (
|
||||||
(this._toBeMoved.length &&
|
elem.group instanceof MindmapElementModel &&
|
||||||
this._toBeMoved.every(
|
!pre.includes(elem.group)
|
||||||
ele => !(ele.group instanceof MindmapElementModel)
|
|
||||||
)) ||
|
|
||||||
(isSingleMindMapNode(this._toBeMoved) &&
|
|
||||||
this._toBeMoved[0].id ===
|
|
||||||
(this._toBeMoved[0].group as MindmapElementModel).tree.id)
|
|
||||||
) {
|
) {
|
||||||
const mindmap = this._toBeMoved[0].group as MindmapElementModel;
|
pre.push(elem.group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pre;
|
||||||
|
},
|
||||||
|
[] as MindmapElementModel[]
|
||||||
|
);
|
||||||
|
|
||||||
this._alignBound = this.snapOverlay.setMovingElements(this._toBeMoved, [
|
this._alignBound = this.snapOverlay.setMovingElements(this._toBeMoved, [
|
||||||
mindmap,
|
...mindmaps,
|
||||||
...(mindmap?.childElements || []),
|
...mindmaps.flatMap(m => m.childElements),
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
this._clearDisposable();
|
this._clearDisposable();
|
||||||
this._disposables = new DisposableGroup();
|
this._disposables = new DisposableGroup();
|
||||||
|
|||||||
@@ -31,10 +31,13 @@ interface Distance {
|
|||||||
|
|
||||||
const ALIGN_THRESHOLD = 8;
|
const ALIGN_THRESHOLD = 8;
|
||||||
const DISTRIBUTION_LINE_OFFSET = 1;
|
const DISTRIBUTION_LINE_OFFSET = 1;
|
||||||
|
const STROKE_WIDTH = 2;
|
||||||
|
|
||||||
export class SnapManager extends Overlay {
|
export class SnapManager extends Overlay {
|
||||||
static override overlayName: string = 'snap-manager';
|
static override overlayName: string = 'snap-manager';
|
||||||
|
|
||||||
|
private _skippedElements: Set<GfxModel> = new Set();
|
||||||
|
|
||||||
private _referenceBounds: {
|
private _referenceBounds: {
|
||||||
vertical: Bound[];
|
vertical: Bound[];
|
||||||
horizontal: Bound[];
|
horizontal: Bound[];
|
||||||
@@ -73,6 +76,7 @@ export class SnapManager extends Overlay {
|
|||||||
};
|
};
|
||||||
this._intraGraphicAlignLines = [];
|
this._intraGraphicAlignLines = [];
|
||||||
this._distributedAlignLines = [];
|
this._distributedAlignLines = [];
|
||||||
|
this._skippedElements.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _alignDistributeHorizontally(
|
private _alignDistributeHorizontally(
|
||||||
@@ -408,6 +412,12 @@ export class SnapManager extends Overlay {
|
|||||||
const bottomToTop = other.minY - bound.maxY;
|
const bottomToTop = other.minY - bound.maxY;
|
||||||
const bottomToBottom = other.maxY - bound.maxY;
|
const bottomToBottom = other.maxY - bound.maxY;
|
||||||
|
|
||||||
|
// calculate side-to-center distances
|
||||||
|
const rightToCenter = other.center[0] - bound.maxX;
|
||||||
|
const leftToCenter = other.center[0] - bound.minX;
|
||||||
|
const topToCenter = other.center[1] - bound.minY;
|
||||||
|
const bottomToCenter = other.center[1] - bound.maxY;
|
||||||
|
|
||||||
const xDistances = [
|
const xDistances = [
|
||||||
centerXDistance,
|
centerXDistance,
|
||||||
leftDistance,
|
leftDistance,
|
||||||
@@ -416,6 +426,8 @@ export class SnapManager extends Overlay {
|
|||||||
leftToRight,
|
leftToRight,
|
||||||
rightToLeft,
|
rightToLeft,
|
||||||
rightToRight,
|
rightToRight,
|
||||||
|
rightToCenter,
|
||||||
|
leftToCenter,
|
||||||
];
|
];
|
||||||
|
|
||||||
const yDistances = [
|
const yDistances = [
|
||||||
@@ -426,6 +438,8 @@ export class SnapManager extends Overlay {
|
|||||||
topToBottom,
|
topToBottom,
|
||||||
bottomToTop,
|
bottomToTop,
|
||||||
bottomToBottom,
|
bottomToBottom,
|
||||||
|
topToCenter,
|
||||||
|
bottomToCenter,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Get absolute distances
|
// Get absolute distances
|
||||||
@@ -487,14 +501,17 @@ export class SnapManager extends Overlay {
|
|||||||
|
|
||||||
const { distance: dx, alignPositionIndices: distanceIndices } =
|
const { distance: dx, alignPositionIndices: distanceIndices } =
|
||||||
distance.horiz;
|
distance.horiz;
|
||||||
|
const offset = STROKE_WIDTH / this.gfx.viewport.zoom / 2;
|
||||||
const alignXPosition = [
|
const alignXPosition = [
|
||||||
other.center[0],
|
other.center[0],
|
||||||
other.minX,
|
other.minX + offset,
|
||||||
other.maxX,
|
other.maxX - offset,
|
||||||
bound.minX + dx,
|
bound.minX + dx + offset,
|
||||||
bound.minX + dx,
|
bound.minX + dx + offset,
|
||||||
bound.maxX + dx,
|
bound.maxX + dx - offset,
|
||||||
bound.maxX + dx,
|
bound.maxX + dx - offset,
|
||||||
|
other.center[0] - offset,
|
||||||
|
other.center[0] + offset,
|
||||||
];
|
];
|
||||||
|
|
||||||
rst.dx = dx;
|
rst.dx = dx;
|
||||||
@@ -531,14 +548,17 @@ export class SnapManager extends Overlay {
|
|||||||
if (!distance.vert) return;
|
if (!distance.vert) return;
|
||||||
|
|
||||||
const { distance: dy, alignPositionIndices } = distance.vert;
|
const { distance: dy, alignPositionIndices } = distance.vert;
|
||||||
|
const offset = STROKE_WIDTH / this.gfx.viewport.zoom / 2;
|
||||||
const alignXPosition = [
|
const alignXPosition = [
|
||||||
other.center[1],
|
other.center[1] - offset,
|
||||||
other.minY,
|
other.minY + offset,
|
||||||
other.maxY,
|
other.maxY - offset,
|
||||||
bound.minY + dy,
|
bound.minY + dy + offset,
|
||||||
bound.minY + dy,
|
bound.minY + dy + offset,
|
||||||
bound.maxY + dy,
|
bound.maxY + dy - offset,
|
||||||
bound.maxY + dy,
|
bound.maxY + dy - offset,
|
||||||
|
other.center[1] + offset,
|
||||||
|
other.center[1] - offset,
|
||||||
];
|
];
|
||||||
|
|
||||||
rst.dy = dy;
|
rst.dy = dy;
|
||||||
@@ -566,6 +586,7 @@ export class SnapManager extends Overlay {
|
|||||||
|
|
||||||
this._intraGraphicAlignLines = [];
|
this._intraGraphicAlignLines = [];
|
||||||
this._distributedAlignLines = [];
|
this._distributedAlignLines = [];
|
||||||
|
this._updateAlignCandidates(bound);
|
||||||
|
|
||||||
for (const other of this._referenceBounds.all) {
|
for (const other of this._referenceBounds.all) {
|
||||||
const closestDistances = this._calculateClosestDistances(bound, other);
|
const closestDistances = this._calculateClosestDistances(bound, other);
|
||||||
@@ -600,7 +621,7 @@ export class SnapManager extends Overlay {
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
const { viewport } = this.gfx;
|
const { viewport } = this.gfx;
|
||||||
const strokeWidth = 2 / viewport.zoom;
|
const strokeWidth = STROKE_WIDTH / viewport.zoom;
|
||||||
|
|
||||||
ctx.strokeStyle = '#8B5CF6';
|
ctx.strokeStyle = '#8B5CF6';
|
||||||
ctx.lineWidth = strokeWidth;
|
ctx.lineWidth = strokeWidth;
|
||||||
@@ -645,22 +666,10 @@ export class SnapManager extends Overlay {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setMovingElements(
|
private _updateAlignCandidates(movingBound: Bound) {
|
||||||
movingElements: GfxModel[],
|
movingBound = movingBound.expand(ALIGN_THRESHOLD * this.gfx.viewport.zoom);
|
||||||
excludes: GfxModel[] = []
|
|
||||||
): Bound {
|
|
||||||
if (movingElements.length === 0) return new Bound();
|
|
||||||
|
|
||||||
const skipped = new Set(movingElements);
|
|
||||||
excludes.forEach(e => skipped.add(e));
|
|
||||||
|
|
||||||
const viewportBound = this.gfx.viewport.viewportBounds;
|
const viewportBound = this.gfx.viewport.viewportBounds;
|
||||||
const movingBound = movingElements
|
|
||||||
.reduce(
|
|
||||||
(prev, element) => prev.unite(element.elementBound),
|
|
||||||
movingElements[0].elementBound
|
|
||||||
)
|
|
||||||
.expand(ALIGN_THRESHOLD * this.gfx.viewport.zoom);
|
|
||||||
const horizAreaBound = new Bound(
|
const horizAreaBound = new Bound(
|
||||||
Math.min(movingBound.x, viewportBound.x),
|
Math.min(movingBound.x, viewportBound.x),
|
||||||
movingBound.y,
|
movingBound.y,
|
||||||
@@ -674,6 +683,7 @@ export class SnapManager extends Overlay {
|
|||||||
Math.max(movingBound.h, viewportBound.h)
|
Math.max(movingBound.h, viewportBound.h)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { _skippedElements: skipped } = this;
|
||||||
const vertCandidates = this.gfx.grid.search(vertAreaBound, {
|
const vertCandidates = this.gfx.grid.search(vertAreaBound, {
|
||||||
useSet: true,
|
useSet: true,
|
||||||
});
|
});
|
||||||
@@ -703,10 +713,22 @@ export class SnapManager extends Overlay {
|
|||||||
vertical: verticalBounds,
|
vertical: verticalBounds,
|
||||||
all: allBounds,
|
all: allBounds,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setMovingElements(
|
||||||
|
movingElements: GfxModel[],
|
||||||
|
excludes: GfxModel[] = []
|
||||||
|
): Bound {
|
||||||
|
if (movingElements.length === 0) return new Bound();
|
||||||
|
|
||||||
|
const skipped = new Set(movingElements);
|
||||||
|
excludes.forEach(e => skipped.add(e));
|
||||||
|
|
||||||
|
this._skippedElements = skipped;
|
||||||
|
|
||||||
return movingElements.reduce(
|
return movingElements.reduce(
|
||||||
(prev, element) => prev.unite(element.elementBound),
|
(prev, element) => prev.unite(element.elementBound),
|
||||||
Bound.deserialize(movingElements[0].xywh)
|
movingElements[0].elementBound
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user