mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-20 04:34:20 +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 {
|
||||
lineSegment,
|
||||
@@ -31,6 +37,7 @@ import {
|
||||
getHoveredElementForBinding,
|
||||
intersectElementWithLineSegment,
|
||||
isBindableElementInsideOtherBindable,
|
||||
isPointInElement,
|
||||
} from "./collision";
|
||||
import { distanceToElement } from "./distance";
|
||||
import {
|
||||
@@ -524,6 +531,140 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
||||
shiftKey?: 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 } => {
|
||||
const globalBindMode = appState.bindMode || "orbit";
|
||||
const startIdx = 0;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
getGridPoint,
|
||||
invariant,
|
||||
isShallowEqual,
|
||||
getFeatureFlag,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
@@ -2236,9 +2237,12 @@ const pointDraggingUpdates = (
|
||||
nextArrow.endBinding.elementId,
|
||||
)! as ExcalidrawBindableElement)
|
||||
: null;
|
||||
|
||||
const endLocalPoint = startIsDraggingOverEndElement
|
||||
? nextArrow.points[nextArrow.points.length - 1]
|
||||
: endIsDraggingOverStartElement && app.state.bindMode !== "inside"
|
||||
: endIsDraggingOverStartElement &&
|
||||
app.state.bindMode !== "inside" &&
|
||||
getFeatureFlag("COMPLEX_BINDINGS")
|
||||
? nextArrow.points[0]
|
||||
: endBindable
|
||||
? updateBoundPoint(
|
||||
@@ -2266,7 +2270,9 @@ const pointDraggingUpdates = (
|
||||
|
||||
const startLocalPoint = endIsDraggingOverStartElement
|
||||
? nextArrow.points[0]
|
||||
: startIsDraggingOverEndElement && app.state.bindMode !== "inside"
|
||||
: startIsDraggingOverEndElement &&
|
||||
app.state.bindMode !== "inside" &&
|
||||
getFeatureFlag("COMPLEX_BINDINGS")
|
||||
? nextArrow.points[nextArrow.points.length - 1]
|
||||
: startBindable
|
||||
? updateBoundPoint(
|
||||
|
||||
@@ -106,6 +106,7 @@ import {
|
||||
MQ_MAX_TABLET,
|
||||
MQ_MAX_HEIGHT_LANDSCAPE,
|
||||
MQ_MAX_WIDTH_LANDSCAPE,
|
||||
getFeatureFlag,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
@@ -4784,7 +4785,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
|
||||
// Handle Alt key for bind mode
|
||||
if (event.key === KEYS.ALT) {
|
||||
if (event.key === KEYS.ALT && getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||
this.handleSkipBindMode();
|
||||
}
|
||||
|
||||
@@ -4797,7 +4798,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
|
||||
if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) {
|
||||
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||
this.resetDelayedBindMode();
|
||||
}
|
||||
|
||||
this.setState({ isBindingEnabled: false });
|
||||
}
|
||||
|
||||
@@ -5103,7 +5107,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
if (isBindingElement(element)) {
|
||||
if (isBindingElement(element) && getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||
this.handleDelayedBindModeChange(element, hoveredElement);
|
||||
}
|
||||
}
|
||||
@@ -6542,8 +6546,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||
this.handleDelayedBindModeChange(multiElement, hoveredElement);
|
||||
}
|
||||
}
|
||||
|
||||
invariant(
|
||||
this.state.selectedLinearElement,
|
||||
@@ -7348,7 +7354,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
private handleCanvasPointerUp = (
|
||||
event: React.PointerEvent<HTMLCanvasElement>,
|
||||
) => {
|
||||
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||
this.resetDelayedBindMode();
|
||||
}
|
||||
|
||||
this.removePointer(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);
|
||||
}
|
||||
}
|
||||
@@ -9023,12 +9032,16 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||
this.handleDelayedBindModeChange(element, hoveredElement);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
event.altKey &&
|
||||
!this.state.selectedLinearElement?.initialState?.arrowStartIsInside
|
||||
!this.state.selectedLinearElement?.initialState
|
||||
?.arrowStartIsInside &&
|
||||
getFeatureFlag("COMPLEX_BINDINGS")
|
||||
) {
|
||||
this.handleSkipBindMode();
|
||||
}
|
||||
@@ -9784,7 +9797,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
}
|
||||
|
||||
if (getFeatureFlag("COMPLEX_BINDINGS")) {
|
||||
this.resetDelayedBindMode();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
selectedElementsAreBeingDragged: false,
|
||||
|
||||
Reference in New Issue
Block a user