fix: snap line offset & check alignment candidates in real time (#10605)

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