mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-20 12:44:38 +01:00
Simplified binding
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
import { KEYS, arrayToMap, invariant, isTransparent } from "@excalidraw/common";
|
import {
|
||||||
|
KEYS,
|
||||||
|
arrayToMap,
|
||||||
|
getFeatureFlag,
|
||||||
|
invariant,
|
||||||
|
isTransparent,
|
||||||
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
lineSegment,
|
lineSegment,
|
||||||
@@ -31,6 +37,7 @@ import {
|
|||||||
getHoveredElementForBinding,
|
getHoveredElementForBinding,
|
||||||
intersectElementWithLineSegment,
|
intersectElementWithLineSegment,
|
||||||
isBindableElementInsideOtherBindable,
|
isBindableElementInsideOtherBindable,
|
||||||
|
isPointInElement,
|
||||||
} from "./collision";
|
} from "./collision";
|
||||||
import { distanceToElement } from "./distance";
|
import { distanceToElement } from "./distance";
|
||||||
import {
|
import {
|
||||||
@@ -524,6 +531,140 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
|||||||
shiftKey?: boolean;
|
shiftKey?: boolean;
|
||||||
finalize?: boolean;
|
finalize?: boolean;
|
||||||
},
|
},
|
||||||
|
): { start: BindingStrategy; end: BindingStrategy } => {
|
||||||
|
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
|
return getBindingStrategyForDraggingBindingElementEndpoints_complex(
|
||||||
|
arrow,
|
||||||
|
draggingPoints,
|
||||||
|
elementsMap,
|
||||||
|
elements,
|
||||||
|
appState,
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBindingStrategyForDraggingBindingElementEndpoints_simple(
|
||||||
|
arrow,
|
||||||
|
draggingPoints,
|
||||||
|
elementsMap,
|
||||||
|
elements,
|
||||||
|
appState,
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
||||||
|
arrow: NonDeleted<ExcalidrawArrowElement>,
|
||||||
|
draggingPoints: PointsPositionUpdates,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
|
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||||
|
appState: AppState,
|
||||||
|
opts?: {
|
||||||
|
newArrow?: boolean;
|
||||||
|
shiftKey?: boolean;
|
||||||
|
finalize?: boolean;
|
||||||
|
},
|
||||||
|
): { start: BindingStrategy; end: BindingStrategy } => {
|
||||||
|
const startIdx = 0;
|
||||||
|
const endIdx = arrow.points.length - 1;
|
||||||
|
const startDragged = draggingPoints.has(startIdx);
|
||||||
|
const endDragged = draggingPoints.has(endIdx);
|
||||||
|
|
||||||
|
let start: BindingStrategy = { mode: undefined };
|
||||||
|
let end: BindingStrategy = { mode: undefined };
|
||||||
|
|
||||||
|
invariant(
|
||||||
|
arrow.points.length > 1,
|
||||||
|
"Do not attempt to bind linear elements with a single point",
|
||||||
|
);
|
||||||
|
|
||||||
|
// If none of the ends are dragged, we don't change anything
|
||||||
|
if (!startDragged && !endDragged) {
|
||||||
|
return { start, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both ends are dragged, we don't bind to anything
|
||||||
|
// and break existing bindings
|
||||||
|
if (startDragged && endDragged) {
|
||||||
|
return { start: { mode: null }, end: { mode: null } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If binding is disabled and an endpoint is dragged,
|
||||||
|
// we actively break the end binding
|
||||||
|
if (!isBindingEnabled(appState)) {
|
||||||
|
start = startDragged ? { mode: null } : start;
|
||||||
|
end = endDragged ? { mode: null } : end;
|
||||||
|
|
||||||
|
return { start, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle simpler elbow arrow binding
|
||||||
|
if (isElbowArrow(arrow)) {
|
||||||
|
return bindingStrategyForElbowArrowEndpointDragging(
|
||||||
|
arrow,
|
||||||
|
draggingPoints,
|
||||||
|
elementsMap,
|
||||||
|
elements,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const localPoint = draggingPoints.get(
|
||||||
|
startDragged ? startIdx : endIdx,
|
||||||
|
)?.point;
|
||||||
|
invariant(
|
||||||
|
localPoint,
|
||||||
|
`Local point must be defined for ${
|
||||||
|
startDragged ? "start" : "end"
|
||||||
|
} dragging`,
|
||||||
|
);
|
||||||
|
const globalPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
|
arrow,
|
||||||
|
localPoint,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
const hit = getHoveredElementForBinding(
|
||||||
|
globalPoint,
|
||||||
|
elements,
|
||||||
|
elementsMap,
|
||||||
|
(e) => 100, // TODO: Zoom-level
|
||||||
|
);
|
||||||
|
const current: BindingStrategy = hit
|
||||||
|
? isPointInElement(globalPoint, hit, elementsMap)
|
||||||
|
? {
|
||||||
|
mode: "inside",
|
||||||
|
element: hit,
|
||||||
|
focusPoint: globalPoint,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
mode: "orbit",
|
||||||
|
element: hit,
|
||||||
|
focusPoint: opts?.finalize
|
||||||
|
? LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
|
arrow,
|
||||||
|
startDragged ? 0 : -1,
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
|
: globalPoint,
|
||||||
|
}
|
||||||
|
: { mode: null };
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: startDragged ? current : start,
|
||||||
|
end: endDragged ? current : end,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBindingStrategyForDraggingBindingElementEndpoints_complex = (
|
||||||
|
arrow: NonDeleted<ExcalidrawArrowElement>,
|
||||||
|
draggingPoints: PointsPositionUpdates,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
|
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||||
|
appState: AppState,
|
||||||
|
opts?: {
|
||||||
|
newArrow?: boolean;
|
||||||
|
shiftKey?: boolean;
|
||||||
|
finalize?: boolean;
|
||||||
|
},
|
||||||
): { start: BindingStrategy; end: BindingStrategy } => {
|
): { start: BindingStrategy; end: BindingStrategy } => {
|
||||||
const globalBindMode = appState.bindMode || "orbit";
|
const globalBindMode = appState.bindMode || "orbit";
|
||||||
const startIdx = 0;
|
const startIdx = 0;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
getGridPoint,
|
getGridPoint,
|
||||||
invariant,
|
invariant,
|
||||||
isShallowEqual,
|
isShallowEqual,
|
||||||
|
getFeatureFlag,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -2236,9 +2237,12 @@ const pointDraggingUpdates = (
|
|||||||
nextArrow.endBinding.elementId,
|
nextArrow.endBinding.elementId,
|
||||||
)! as ExcalidrawBindableElement)
|
)! as ExcalidrawBindableElement)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const endLocalPoint = startIsDraggingOverEndElement
|
const endLocalPoint = startIsDraggingOverEndElement
|
||||||
? nextArrow.points[nextArrow.points.length - 1]
|
? nextArrow.points[nextArrow.points.length - 1]
|
||||||
: endIsDraggingOverStartElement && app.state.bindMode !== "inside"
|
: endIsDraggingOverStartElement &&
|
||||||
|
app.state.bindMode !== "inside" &&
|
||||||
|
getFeatureFlag("COMPLEX_BINDINGS")
|
||||||
? nextArrow.points[0]
|
? nextArrow.points[0]
|
||||||
: endBindable
|
: endBindable
|
||||||
? updateBoundPoint(
|
? updateBoundPoint(
|
||||||
@@ -2266,7 +2270,9 @@ const pointDraggingUpdates = (
|
|||||||
|
|
||||||
const startLocalPoint = endIsDraggingOverStartElement
|
const startLocalPoint = endIsDraggingOverStartElement
|
||||||
? nextArrow.points[0]
|
? nextArrow.points[0]
|
||||||
: startIsDraggingOverEndElement && app.state.bindMode !== "inside"
|
: startIsDraggingOverEndElement &&
|
||||||
|
app.state.bindMode !== "inside" &&
|
||||||
|
getFeatureFlag("COMPLEX_BINDINGS")
|
||||||
? nextArrow.points[nextArrow.points.length - 1]
|
? nextArrow.points[nextArrow.points.length - 1]
|
||||||
: startBindable
|
: startBindable
|
||||||
? updateBoundPoint(
|
? updateBoundPoint(
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ import {
|
|||||||
MQ_MAX_TABLET,
|
MQ_MAX_TABLET,
|
||||||
MQ_MAX_HEIGHT_LANDSCAPE,
|
MQ_MAX_HEIGHT_LANDSCAPE,
|
||||||
MQ_MAX_WIDTH_LANDSCAPE,
|
MQ_MAX_WIDTH_LANDSCAPE,
|
||||||
|
getFeatureFlag,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -4784,7 +4785,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle Alt key for bind mode
|
// Handle Alt key for bind mode
|
||||||
if (event.key === KEYS.ALT) {
|
if (event.key === KEYS.ALT && getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
this.handleSkipBindMode();
|
this.handleSkipBindMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4797,7 +4798,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) {
|
if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) {
|
||||||
|
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
this.resetDelayedBindMode();
|
this.resetDelayedBindMode();
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ isBindingEnabled: false });
|
this.setState({ isBindingEnabled: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5103,7 +5107,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isBindingElement(element)) {
|
if (isBindingElement(element) && getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
this.handleDelayedBindModeChange(element, hoveredElement);
|
this.handleDelayedBindModeChange(element, hoveredElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6542,8 +6546,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
this.handleDelayedBindModeChange(multiElement, hoveredElement);
|
this.handleDelayedBindModeChange(multiElement, hoveredElement);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
invariant(
|
invariant(
|
||||||
this.state.selectedLinearElement,
|
this.state.selectedLinearElement,
|
||||||
@@ -7348,7 +7354,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
private handleCanvasPointerUp = (
|
private handleCanvasPointerUp = (
|
||||||
event: React.PointerEvent<HTMLCanvasElement>,
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
) => {
|
) => {
|
||||||
|
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
this.resetDelayedBindMode();
|
this.resetDelayedBindMode();
|
||||||
|
}
|
||||||
|
|
||||||
this.removePointer(event);
|
this.removePointer(event);
|
||||||
this.lastPointerUpEvent = event;
|
this.lastPointerUpEvent = event;
|
||||||
|
|
||||||
@@ -8655,7 +8664,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isBindingElement(element)) {
|
if (isBindingElement(element) && getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
this.handleDelayedBindModeChange(element, boundElement);
|
this.handleDelayedBindModeChange(element, boundElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9023,12 +9032,16 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
this.handleDelayedBindModeChange(element, hoveredElement);
|
this.handleDelayedBindModeChange(element, hoveredElement);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event.altKey &&
|
event.altKey &&
|
||||||
!this.state.selectedLinearElement?.initialState?.arrowStartIsInside
|
!this.state.selectedLinearElement?.initialState
|
||||||
|
?.arrowStartIsInside &&
|
||||||
|
getFeatureFlag("COMPLEX_BINDINGS")
|
||||||
) {
|
) {
|
||||||
this.handleSkipBindMode();
|
this.handleSkipBindMode();
|
||||||
}
|
}
|
||||||
@@ -9784,7 +9797,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||||
this.resetDelayedBindMode();
|
this.resetDelayedBindMode();
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedElementsAreBeingDragged: false,
|
selectedElementsAreBeingDragged: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user