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

View File

@@ -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();

View File

@@ -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
); );
} }
} }