Skip to content

Commit

Permalink
fix: object snapping not working (excalidraw#8381)
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelle authored Aug 15, 2024
1 parent 3cfcc7b commit fb4bb29
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 46 deletions.
21 changes: 9 additions & 12 deletions packages/excalidraw/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ import {
getVisibleGaps,
getReferenceSnapPoints,
SnapCache,
isGridModeEnabled,
} from "../snapping";
import { actionWrapTextInContainer } from "../actions/actionBoundText";
import BraveMeasureTextError from "./BraveMeasureTextError";
Expand Down Expand Up @@ -818,9 +819,7 @@ class App extends React.Component<AppProps, AppState> {
*/
public getEffectiveGridSize = () => {
return (
this.props.gridModeEnabled ?? this.state.gridModeEnabled
? this.state.gridSize
: null
isGridModeEnabled(this) ? this.state.gridSize : null
) as NullableGridSize;
};

Expand Down Expand Up @@ -1696,9 +1695,7 @@ class App extends React.Component<AppProps, AppState> {
renderConfig={{
imageCache: this.imageCache,
isExporting: false,
renderGrid:
this.props.gridModeEnabled ??
this.state.gridModeEnabled,
renderGrid: isGridModeEnabled(this),
canvasBackgroundColor:
this.state.viewBackgroundColor,
embedsValidationStatus: this.embedsValidationStatus,
Expand Down Expand Up @@ -5452,7 +5449,7 @@ class App extends React.Component<AppProps, AppState> {
) {
const { originOffset, snapLines } = getSnapLinesAtPointer(
this.scene.getNonDeletedElements(),
this.state,
this,
{
x: scenePointerX,
y: scenePointerY,
Expand Down Expand Up @@ -7499,7 +7496,7 @@ class App extends React.Component<AppProps, AppState> {
if (
isSnappingEnabled({
event,
appState: this.state,
app: this,
selectedElements,
}) &&
(recomputeAnyways || !SnapCache.getReferenceSnapPoints())
Expand All @@ -7523,7 +7520,7 @@ class App extends React.Component<AppProps, AppState> {
if (
isSnappingEnabled({
event,
appState: this.state,
app: this,
selectedElements,
}) &&
(recomputeAnyways || !SnapCache.getVisibleGaps())
Expand Down Expand Up @@ -7811,7 +7808,7 @@ class App extends React.Component<AppProps, AppState> {
const { snapOffset, snapLines } = snapDraggedElements(
originalElements,
dragOffset,
this.state,
this,
event,
this.scene.getNonDeletedElementsMap(),
);
Expand Down Expand Up @@ -9812,7 +9809,7 @@ class App extends React.Component<AppProps, AppState> {

const { snapOffset, snapLines } = snapNewElement(
newElement,
this.state,
this,
event,
{
x:
Expand Down Expand Up @@ -9949,7 +9946,7 @@ class App extends React.Component<AppProps, AppState> {
const { snapOffset, snapLines } = snapResizingElements(
selectedElements,
getSelectedElements(originalElements, this.state),
this.state,
this,
event,
dragOffset,
transformHandleType,
Expand Down
3 changes: 2 additions & 1 deletion packages/excalidraw/components/HintViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { isEraserActive } from "../appState";

import "./HintViewer.scss";
import { isNodeInFlowchart } from "../element/flowchart";
import { isGridModeEnabled } from "../snapping";

interface HintViewerProps {
appState: UIAppState;
Expand Down Expand Up @@ -100,7 +101,7 @@ const getHints = ({
return t("hints.deepBoxSelect");
}

if (appState.gridSize && appState.selectedElementsAreBeingDragged) {
if (isGridModeEnabled(app) && appState.selectedElementsAreBeingDragged) {
return t("hints.disableSnapping");
}

Expand Down
4 changes: 2 additions & 2 deletions packages/excalidraw/components/Stats/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import CanvasGrid from "./CanvasGrid";
import clsx from "clsx";

import "./Stats.scss";
import { isGridModeEnabled } from "../../snapping";

interface StatsProps {
app: AppClassProperties;
Expand All @@ -44,8 +45,7 @@ export const Stats = (props: StatsProps) => {
selectedElementIds: appState.selectedElementIds,
includeBoundTextElement: false,
});
const gridModeEnabled =
props.app.props.gridModeEnabled ?? appState.gridModeEnabled;
const gridModeEnabled = isGridModeEnabled(props.app);

return (
<StatsInner
Expand Down
71 changes: 40 additions & 31 deletions packages/excalidraw/snapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import {
getSelectedElements,
getVisibleAndNonSelectedElements,
} from "./scene/selection";
import type { AppState, KeyboardModifiersObject, Point } from "./types";
import type {
AppClassProperties,
AppState,
KeyboardModifiersObject,
Point,
} from "./types";

const SNAP_DISTANCE = 8;

Expand Down Expand Up @@ -139,29 +144,32 @@ export class SnapCache {

// -----------------------------------------------------------------------------

export const isGridModeEnabled = (app: AppClassProperties): boolean =>
app.props.gridModeEnabled ?? app.state.gridModeEnabled;

export const isSnappingEnabled = ({
event,
appState,
app,
selectedElements,
}: {
appState: AppState;
app: AppClassProperties;
event: KeyboardModifiersObject;
selectedElements: NonDeletedExcalidrawElement[];
}) => {
if (event) {
return (
(appState.objectsSnapModeEnabled && !event[KEYS.CTRL_OR_CMD]) ||
(!appState.objectsSnapModeEnabled &&
(app.state.objectsSnapModeEnabled && !event[KEYS.CTRL_OR_CMD]) ||
(!app.state.objectsSnapModeEnabled &&
event[KEYS.CTRL_OR_CMD] &&
appState.gridSize === null)
!isGridModeEnabled(app))
);
}

// do not suggest snaps for an arrow to give way to binding
if (selectedElements.length === 1 && selectedElements[0].type === "arrow") {
return false;
}
return appState.objectsSnapModeEnabled;
return app.state.objectsSnapModeEnabled;
};

export const areRoughlyEqual = (a: number, b: number, precision = 0.01) => {
Expand Down Expand Up @@ -406,13 +414,13 @@ export const getVisibleGaps = (
const getGapSnaps = (
selectedElements: ExcalidrawElement[],
dragOffset: Vector2D,
appState: AppState,
app: AppClassProperties,
event: KeyboardModifiersObject,
nearestSnapsX: Snaps,
nearestSnapsY: Snaps,
minOffset: Vector2D,
) => {
if (!isSnappingEnabled({ appState, event, selectedElements })) {
if (!isSnappingEnabled({ app, event, selectedElements })) {
return [];
}

Expand Down Expand Up @@ -596,14 +604,14 @@ export const getReferenceSnapPoints = (
const getPointSnaps = (
selectedElements: ExcalidrawElement[],
selectionSnapPoints: Point[],
appState: AppState,
app: AppClassProperties,
event: KeyboardModifiersObject,
nearestSnapsX: Snaps,
nearestSnapsY: Snaps,
minOffset: Vector2D,
) => {
if (
!isSnappingEnabled({ appState, event, selectedElements }) ||
!isSnappingEnabled({ app, event, selectedElements }) ||
(selectedElements.length === 0 && selectionSnapPoints.length === 0)
) {
return [];
Expand Down Expand Up @@ -652,13 +660,14 @@ const getPointSnaps = (
export const snapDraggedElements = (
elements: ExcalidrawElement[],
dragOffset: Vector2D,
appState: AppState,
app: AppClassProperties,
event: KeyboardModifiersObject,
elementsMap: ElementsMap,
) => {
const appState = app.state;
const selectedElements = getSelectedElements(elements, appState);
if (
!isSnappingEnabled({ appState, event, selectedElements }) ||
!isSnappingEnabled({ app, event, selectedElements }) ||
selectedElements.length === 0
) {
return {
Expand Down Expand Up @@ -687,7 +696,7 @@ export const snapDraggedElements = (
getPointSnaps(
selectedElements,
selectionPoints,
appState,
app,
event,
nearestSnapsX,
nearestSnapsY,
Expand All @@ -697,7 +706,7 @@ export const snapDraggedElements = (
getGapSnaps(
selectedElements,
dragOffset,
appState,
app,
event,
nearestSnapsX,
nearestSnapsY,
Expand Down Expand Up @@ -732,7 +741,7 @@ export const snapDraggedElements = (
getElementsCorners(selectedElements, elementsMap, {
dragOffset: newDragOffset,
}),
appState,
app,
event,
nearestSnapsX,
nearestSnapsY,
Expand All @@ -742,7 +751,7 @@ export const snapDraggedElements = (
getGapSnaps(
selectedElements,
newDragOffset,
appState,
app,
event,
nearestSnapsX,
nearestSnapsY,
Expand Down Expand Up @@ -1075,13 +1084,13 @@ export const snapResizingElements = (
selectedElements: ExcalidrawElement[],
// while using the original elements to appy dragOffset to calculate snaps
selectedOriginalElements: ExcalidrawElement[],
appState: AppState,
app: AppClassProperties,
event: KeyboardModifiersObject,
dragOffset: Vector2D,
transformHandle: MaybeTransformHandleType,
) => {
if (
!isSnappingEnabled({ event, selectedElements, appState }) ||
!isSnappingEnabled({ event, selectedElements, app }) ||
selectedElements.length === 0 ||
(selectedElements.length === 1 &&
!areRoughlyEqual(selectedElements[0].angle, 0))
Expand Down Expand Up @@ -1147,7 +1156,7 @@ export const snapResizingElements = (
}
}

const snapDistance = getSnapDistance(appState.zoom.value);
const snapDistance = getSnapDistance(app.state.zoom.value);

const minOffset = {
x: snapDistance,
Expand All @@ -1160,7 +1169,7 @@ export const snapResizingElements = (
getPointSnaps(
selectedOriginalElements,
selectionSnapPoints,
appState,
app,
event,
nearestSnapsX,
nearestSnapsY,
Expand Down Expand Up @@ -1193,7 +1202,7 @@ export const snapResizingElements = (
getPointSnaps(
selectedElements,
corners,
appState,
app,
event,
nearestSnapsX,
nearestSnapsY,
Expand All @@ -1210,13 +1219,13 @@ export const snapResizingElements = (

export const snapNewElement = (
newElement: ExcalidrawElement,
appState: AppState,
app: AppClassProperties,
event: KeyboardModifiersObject,
origin: Vector2D,
dragOffset: Vector2D,
elementsMap: ElementsMap,
) => {
if (!isSnappingEnabled({ event, selectedElements: [newElement], appState })) {
if (!isSnappingEnabled({ event, selectedElements: [newElement], app })) {
return {
snapOffset: { x: 0, y: 0 },
snapLines: [],
Expand All @@ -1227,7 +1236,7 @@ export const snapNewElement = (
[origin.x + dragOffset.x, origin.y + dragOffset.y],
];

const snapDistance = getSnapDistance(appState.zoom.value);
const snapDistance = getSnapDistance(app.state.zoom.value);

const minOffset = {
x: snapDistance,
Expand All @@ -1240,7 +1249,7 @@ export const snapNewElement = (
getPointSnaps(
[newElement],
selectionSnapPoints,
appState,
app,
event,
nearestSnapsX,
nearestSnapsY,
Expand All @@ -1265,7 +1274,7 @@ export const snapNewElement = (
getPointSnaps(
[newElement],
corners,
appState,
app,
event,
nearestSnapsX,
nearestSnapsY,
Expand All @@ -1282,12 +1291,12 @@ export const snapNewElement = (

export const getSnapLinesAtPointer = (
elements: readonly ExcalidrawElement[],
appState: AppState,
app: AppClassProperties,
pointer: Vector2D,
event: KeyboardModifiersObject,
elementsMap: ElementsMap,
) => {
if (!isSnappingEnabled({ event, selectedElements: [], appState })) {
if (!isSnappingEnabled({ event, selectedElements: [], app })) {
return {
originOffset: { x: 0, y: 0 },
snapLines: [],
Expand All @@ -1297,11 +1306,11 @@ export const getSnapLinesAtPointer = (
const referenceElements = getVisibleAndNonSelectedElements(
elements,
[],
appState,
app.state,
elementsMap,
);

const snapDistance = getSnapDistance(appState.zoom.value);
const snapDistance = getSnapDistance(app.state.zoom.value);

const minOffset = {
x: snapDistance,
Expand Down

0 comments on commit fb4bb29

Please sign in to comment.