mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
fix: Elbow arrow fixedpoint flipping now properly flips on inverted resize and flip action (#8324)
* Flipping action now properly mirrors selections with elbow arrows * Flipping action now re-centers the selection to the original center to avoid "walking" selections on repeated flipping
This commit is contained in:
parent
44a1c8d857
commit
f3f0ab7c83
17 changed files with 290 additions and 85 deletions
|
@ -39,6 +39,7 @@ import {
|
|||
isBindingElement,
|
||||
isBoundToContainer,
|
||||
isElbowArrow,
|
||||
isFixedPointBinding,
|
||||
isFrameLikeElement,
|
||||
isLinearElement,
|
||||
isRectangularElement,
|
||||
|
@ -797,7 +798,7 @@ export const bindPointToSnapToElementOutline = (
|
|||
isVertical
|
||||
? Math.abs(p[1] - i[1]) < 0.1
|
||||
: Math.abs(p[0] - i[0]) < 0.1,
|
||||
)[0] ?? point;
|
||||
)[0] ?? p;
|
||||
}
|
||||
|
||||
return p;
|
||||
|
@ -1013,7 +1014,7 @@ const updateBoundPoint = (
|
|||
const direction = startOrEnd === "startBinding" ? -1 : 1;
|
||||
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
||||
|
||||
if (isElbowArrow(linearElement)) {
|
||||
if (isElbowArrow(linearElement) && isFixedPointBinding(binding)) {
|
||||
const fixedPoint =
|
||||
normalizeFixedPoint(binding.fixedPoint) ??
|
||||
calculateFixedPointForElbowArrowBinding(
|
||||
|
|
|
@ -35,7 +35,6 @@ export const dragSelectedElements = (
|
|||
) => {
|
||||
if (
|
||||
_selectedElements.length === 1 &&
|
||||
isArrowElement(_selectedElements[0]) &&
|
||||
isElbowArrow(_selectedElements[0]) &&
|
||||
(_selectedElements[0].startBinding || _selectedElements[0].endBinding)
|
||||
) {
|
||||
|
@ -43,13 +42,7 @@ export const dragSelectedElements = (
|
|||
}
|
||||
|
||||
const selectedElements = _selectedElements.filter(
|
||||
(el) =>
|
||||
!(
|
||||
isArrowElement(el) &&
|
||||
isElbowArrow(el) &&
|
||||
el.startBinding &&
|
||||
el.endBinding
|
||||
),
|
||||
(el) => !(isElbowArrow(el) && el.startBinding && el.endBinding),
|
||||
);
|
||||
|
||||
// we do not want a frame and its elements to be selected at the same time
|
||||
|
|
|
@ -102,6 +102,7 @@ export class LinearElementEditor {
|
|||
public readonly endBindingElement: ExcalidrawBindableElement | null | "keep";
|
||||
public readonly hoverPointIndex: number;
|
||||
public readonly segmentMidPointHoveredCoords: GlobalPoint | null;
|
||||
public readonly elbowed: boolean;
|
||||
|
||||
constructor(element: NonDeleted<ExcalidrawLinearElement>) {
|
||||
this.elementId = element.id as string & {
|
||||
|
@ -131,6 +132,7 @@ export class LinearElementEditor {
|
|||
};
|
||||
this.hoverPointIndex = -1;
|
||||
this.segmentMidPointHoveredCoords = null;
|
||||
this.elbowed = isElbowArrow(element) && element.elbowed;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -1477,7 +1479,9 @@ export class LinearElementEditor {
|
|||
nextPoints,
|
||||
vector(offsetX, offsetY),
|
||||
bindings,
|
||||
options,
|
||||
{
|
||||
isDragging: options?.isDragging,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
const nextCoords = getElementPointsCoords(element, nextPoints);
|
||||
|
|
|
@ -9,6 +9,7 @@ import type {
|
|||
ExcalidrawTextElementWithContainer,
|
||||
ExcalidrawImageElement,
|
||||
ElementsMap,
|
||||
ExcalidrawArrowElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
SceneElementsMap,
|
||||
} from "./types";
|
||||
|
@ -909,6 +910,8 @@ export const resizeMultipleElements = (
|
|||
fontSize?: ExcalidrawTextElement["fontSize"];
|
||||
scale?: ExcalidrawImageElement["scale"];
|
||||
boundTextFontSize?: ExcalidrawTextElement["fontSize"];
|
||||
startBinding?: ExcalidrawArrowElement["startBinding"];
|
||||
endBinding?: ExcalidrawArrowElement["endBinding"];
|
||||
};
|
||||
}[] = [];
|
||||
|
||||
|
@ -993,19 +996,6 @@ export const resizeMultipleElements = (
|
|||
|
||||
mutateElement(element, update, false);
|
||||
|
||||
if (isArrowElement(element) && isElbowArrow(element)) {
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
elementsMap,
|
||||
element.points,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
informMutation: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
updateBoundElements(element, elementsMap, {
|
||||
simultaneouslyUpdated: elementsToUpdate,
|
||||
oldSize: { width: oldWidth, height: oldHeight },
|
||||
|
@ -1059,7 +1049,7 @@ const rotateMultipleElements = (
|
|||
(centerAngle + origAngle - element.angle) as Radians,
|
||||
);
|
||||
|
||||
if (isArrowElement(element) && isElbowArrow(element)) {
|
||||
if (isElbowArrow(element)) {
|
||||
const points = getArrowLocalFixedPoints(element, elementsMap);
|
||||
mutateElbowArrow(element, elementsMap, points);
|
||||
} else {
|
||||
|
|
|
@ -41,7 +41,6 @@ import { mutateElement } from "./mutateElement";
|
|||
import { isBindableElement, isRectanguloidElement } from "./typeChecks";
|
||||
import type {
|
||||
ExcalidrawElbowArrowElement,
|
||||
FixedPointBinding,
|
||||
NonDeletedSceneElementsMap,
|
||||
SceneElementsMap,
|
||||
} from "./types";
|
||||
|
@ -73,13 +72,12 @@ export const mutateElbowArrow = (
|
|||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
|
||||
nextPoints: readonly LocalPoint[],
|
||||
offset?: Vector,
|
||||
otherUpdates?: {
|
||||
startBinding?: FixedPointBinding | null;
|
||||
endBinding?: FixedPointBinding | null;
|
||||
},
|
||||
otherUpdates?: Omit<
|
||||
ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
"angle" | "x" | "y" | "width" | "height" | "elbowed" | "points"
|
||||
>,
|
||||
options?: {
|
||||
isDragging?: boolean;
|
||||
disableBinding?: boolean;
|
||||
informMutation?: boolean;
|
||||
},
|
||||
) => {
|
||||
|
|
|
@ -320,9 +320,12 @@ export const getDefaultRoundnessTypeForElement = (
|
|||
};
|
||||
|
||||
export const isFixedPointBinding = (
|
||||
binding: PointBinding,
|
||||
binding: PointBinding | FixedPointBinding,
|
||||
): binding is FixedPointBinding => {
|
||||
return binding.fixedPoint != null;
|
||||
return (
|
||||
Object.hasOwn(binding, "fixedPoint") &&
|
||||
(binding as FixedPointBinding).fixedPoint != null
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: Move this to @excalidraw/math
|
||||
|
|
|
@ -193,6 +193,7 @@ export type ExcalidrawElement =
|
|||
| ExcalidrawGenericElement
|
||||
| ExcalidrawTextElement
|
||||
| ExcalidrawLinearElement
|
||||
| ExcalidrawArrowElement
|
||||
| ExcalidrawFreeDrawElement
|
||||
| ExcalidrawImageElement
|
||||
| ExcalidrawFrameElement
|
||||
|
@ -268,15 +269,19 @@ export type PointBinding = {
|
|||
elementId: ExcalidrawBindableElement["id"];
|
||||
focus: number;
|
||||
gap: number;
|
||||
// Represents the fixed point binding information in form of a vertical and
|
||||
// horizontal ratio (i.e. a percentage value in the 0.0-1.0 range). This ratio
|
||||
// gives the user selected fixed point by multiplying the bound element width
|
||||
// with fixedPoint[0] and the bound element height with fixedPoint[1] to get the
|
||||
// bound element-local point coordinate.
|
||||
fixedPoint: FixedPoint | null;
|
||||
};
|
||||
|
||||
export type FixedPointBinding = Merge<PointBinding, { fixedPoint: FixedPoint }>;
|
||||
export type FixedPointBinding = Merge<
|
||||
PointBinding,
|
||||
{
|
||||
// Represents the fixed point binding information in form of a vertical and
|
||||
// horizontal ratio (i.e. a percentage value in the 0.0-1.0 range). This ratio
|
||||
// gives the user selected fixed point by multiplying the bound element width
|
||||
// with fixedPoint[0] and the bound element height with fixedPoint[1] to get the
|
||||
// bound element-local point coordinate.
|
||||
fixedPoint: FixedPoint;
|
||||
}
|
||||
>;
|
||||
|
||||
export type Arrowhead =
|
||||
| "arrow"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue