POC: auto-transform to polygon on bg set
This commit is contained in:
parent
a9a2c953b4
commit
1fdf8967ed
@ -5,6 +5,7 @@ import {
|
||||
ROUNDNESS,
|
||||
invariant,
|
||||
elementCenterPoint,
|
||||
MIN_LOOP_LOCK_DISTANCE,
|
||||
} from "@excalidraw/common";
|
||||
import {
|
||||
isPoint,
|
||||
@ -39,6 +40,7 @@ import type {
|
||||
ElementsMap,
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawLineElement,
|
||||
NonDeleted,
|
||||
} from "./types";
|
||||
|
||||
@ -396,3 +398,81 @@ export const isPathALoop = (
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const toggleLinePolygonState = (
|
||||
element: ExcalidrawLineElement,
|
||||
nextPolygonState: boolean,
|
||||
) => {
|
||||
const updatedPoints = [...element.points];
|
||||
|
||||
if (nextPolygonState) {
|
||||
const firstPoint = updatedPoints[0];
|
||||
const lastPoint = updatedPoints[updatedPoints.length - 1];
|
||||
|
||||
const distance = Math.hypot(
|
||||
firstPoint[0] - lastPoint[0],
|
||||
firstPoint[1] - lastPoint[1],
|
||||
);
|
||||
|
||||
if (distance > MIN_LOOP_LOCK_DISTANCE) {
|
||||
updatedPoints.push(pointFrom(firstPoint[0], firstPoint[1]));
|
||||
} else {
|
||||
updatedPoints[updatedPoints.length - 1] = pointFrom(
|
||||
firstPoint[0],
|
||||
firstPoint[1],
|
||||
);
|
||||
}
|
||||
} else if (element.loopLock) {
|
||||
// When toggling from loopLock=true to loopLock=false
|
||||
// We need to dislocate the end point by 15 points
|
||||
|
||||
// When loopLock is true, the last point is the same as the first point
|
||||
// We'll use the direction from second-to-last point to first point
|
||||
const firstPoint = updatedPoints[0];
|
||||
|
||||
if (updatedPoints.length >= 3) {
|
||||
const secondLastPoint = updatedPoints[updatedPoints.length - 2];
|
||||
|
||||
// Get direction from second-last to first
|
||||
const dx = firstPoint[0] - secondLastPoint[0];
|
||||
const dy = firstPoint[1] - secondLastPoint[1];
|
||||
|
||||
// Calculate perpendicular direction (rotate 90 degrees)
|
||||
// This creates a visible gap perpendicular to the line direction
|
||||
const perpDx = dy;
|
||||
const perpDy = -dx;
|
||||
|
||||
// Normalize the perpendicular direction vector
|
||||
const perpLength = Math.sqrt(perpDx * perpDx + perpDy * perpDy);
|
||||
let normalizedPerpDx = 0;
|
||||
let normalizedPerpDy = 0;
|
||||
|
||||
if (perpLength > 0) {
|
||||
normalizedPerpDx = perpDx / perpLength;
|
||||
normalizedPerpDy = perpDy / perpLength;
|
||||
} else {
|
||||
// Default perpendicular if points are the same
|
||||
normalizedPerpDx = -0.7071;
|
||||
normalizedPerpDy = 0.7071;
|
||||
}
|
||||
|
||||
// Move the end point perpendicular to the line direction
|
||||
updatedPoints[updatedPoints.length - 1] = pointFrom(
|
||||
firstPoint[0] + normalizedPerpDx * 15,
|
||||
firstPoint[1] + normalizedPerpDy * 15,
|
||||
);
|
||||
} else {
|
||||
// For simple lines with fewer than 3 points
|
||||
// Just move away from the first point at a 45-degree angle
|
||||
updatedPoints[updatedPoints.length - 1] = pointFrom(
|
||||
firstPoint[0] + 10.6,
|
||||
firstPoint[1] - 10.6, // Different direction to avoid crossing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loopLock: nextPolygonState,
|
||||
points: updatedPoints,
|
||||
};
|
||||
};
|
||||
|
@ -5,9 +5,6 @@ import {
|
||||
isLineElement,
|
||||
} from "@excalidraw/element/typeChecks";
|
||||
import { arrayToMap } from "@excalidraw/common";
|
||||
import { MIN_LOOP_LOCK_DISTANCE } from "@excalidraw/common";
|
||||
|
||||
import { pointFrom } from "@excalidraw/math";
|
||||
|
||||
import type {
|
||||
ExcalidrawLinearElement,
|
||||
@ -26,6 +23,10 @@ import { CaptureUpdateAction } from "../store";
|
||||
|
||||
import { ButtonIcon } from "../components/ButtonIcon";
|
||||
|
||||
import { newElementWith } from "../../element/src/mutateElement";
|
||||
|
||||
import { toggleLinePolygonState } from "../../element/src/shapes";
|
||||
|
||||
import { register } from "./register";
|
||||
|
||||
export const actionToggleLinearEditor = register({
|
||||
@ -96,85 +97,6 @@ export const actionToggleLinearEditor = register({
|
||||
},
|
||||
});
|
||||
|
||||
const updateLoopLock = (
|
||||
element: ExcalidrawLineElement,
|
||||
newLoopLockState: boolean,
|
||||
app: any,
|
||||
) => {
|
||||
const updatedPoints = [...element.points];
|
||||
|
||||
if (newLoopLockState) {
|
||||
const firstPoint = updatedPoints[0];
|
||||
const lastPoint = updatedPoints[updatedPoints.length - 1];
|
||||
|
||||
const distance = Math.hypot(
|
||||
firstPoint[0] - lastPoint[0],
|
||||
firstPoint[1] - lastPoint[1],
|
||||
);
|
||||
|
||||
if (distance > MIN_LOOP_LOCK_DISTANCE) {
|
||||
updatedPoints.push(pointFrom(firstPoint[0], firstPoint[1]));
|
||||
} else {
|
||||
updatedPoints[updatedPoints.length - 1] = pointFrom(
|
||||
firstPoint[0],
|
||||
firstPoint[1],
|
||||
);
|
||||
}
|
||||
} else if (element.loopLock) {
|
||||
// When toggling from loopLock=true to loopLock=false
|
||||
// We need to dislocate the end point by 15 points
|
||||
|
||||
// When loopLock is true, the last point is the same as the first point
|
||||
// We'll use the direction from second-to-last point to first point
|
||||
const firstPoint = updatedPoints[0];
|
||||
|
||||
if (updatedPoints.length >= 3) {
|
||||
const secondLastPoint = updatedPoints[updatedPoints.length - 2];
|
||||
|
||||
// Get direction from second-last to first
|
||||
const dx = firstPoint[0] - secondLastPoint[0];
|
||||
const dy = firstPoint[1] - secondLastPoint[1];
|
||||
|
||||
// Calculate perpendicular direction (rotate 90 degrees)
|
||||
// This creates a visible gap perpendicular to the line direction
|
||||
const perpDx = dy;
|
||||
const perpDy = -dx;
|
||||
|
||||
// Normalize the perpendicular direction vector
|
||||
const perpLength = Math.sqrt(perpDx * perpDx + perpDy * perpDy);
|
||||
let normalizedPerpDx = 0;
|
||||
let normalizedPerpDy = 0;
|
||||
|
||||
if (perpLength > 0) {
|
||||
normalizedPerpDx = perpDx / perpLength;
|
||||
normalizedPerpDy = perpDy / perpLength;
|
||||
} else {
|
||||
// Default perpendicular if points are the same
|
||||
normalizedPerpDx = -0.7071;
|
||||
normalizedPerpDy = 0.7071;
|
||||
}
|
||||
|
||||
// Move the end point perpendicular to the line direction
|
||||
updatedPoints[updatedPoints.length - 1] = pointFrom(
|
||||
firstPoint[0] + normalizedPerpDx * 15,
|
||||
firstPoint[1] + normalizedPerpDy * 15,
|
||||
);
|
||||
} else {
|
||||
// For simple lines with fewer than 3 points
|
||||
// Just move away from the first point at a 45-degree angle
|
||||
updatedPoints[updatedPoints.length - 1] = pointFrom(
|
||||
firstPoint[0] + 10.6,
|
||||
firstPoint[1] - 10.6, // Different direction to avoid crossing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app.scene.mutateElement(element, {
|
||||
loopLock: newLoopLockState,
|
||||
points: updatedPoints,
|
||||
});
|
||||
};
|
||||
|
||||
export const actionToggleLoopLock = register({
|
||||
name: "toggleLoopLock",
|
||||
category: DEFAULT_CATEGORIES.elements,
|
||||
@ -208,28 +130,33 @@ export const actionToggleLoopLock = register({
|
||||
);
|
||||
},
|
||||
perform(elements, appState, _, app) {
|
||||
const selectedElements = app.scene
|
||||
.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
})
|
||||
.filter((element) => isLineElement(element)) as ExcalidrawLineElement[];
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
if (!selectedElements.length) {
|
||||
if (selectedElements.some((element) => !isLineElement(element))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetElements = selectedElements as ExcalidrawLineElement[];
|
||||
|
||||
// Check if we should lock or unlock based on current state
|
||||
// If all elements are locked, unlock all. Otherwise, lock all.
|
||||
const allLocked = selectedElements.every((element) => element.loopLock);
|
||||
const allLocked = targetElements.every((element) => element.loopLock);
|
||||
const newLoopLockState = !allLocked;
|
||||
|
||||
selectedElements.forEach((element) => {
|
||||
updateLoopLock(element, newLoopLockState, app);
|
||||
});
|
||||
const targetElementsMap = arrayToMap(targetElements);
|
||||
|
||||
return {
|
||||
elements: elements.map((element) => {
|
||||
if (!targetElementsMap.has(element.id) || !isLineElement(element)) {
|
||||
return element;
|
||||
}
|
||||
|
||||
return newElementWith(
|
||||
element,
|
||||
toggleLinePolygonState(element, newLoopLockState),
|
||||
);
|
||||
}),
|
||||
appState,
|
||||
elements,
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
getShortcutKey,
|
||||
tupleToCoors,
|
||||
getLineHeight,
|
||||
isTransparent,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { getNonDeletedElements } from "@excalidraw/element";
|
||||
@ -46,6 +47,7 @@ import {
|
||||
isBoundToContainer,
|
||||
isElbowArrow,
|
||||
isLinearElement,
|
||||
isLineElement,
|
||||
isTextElement,
|
||||
isUsingAdaptiveRadius,
|
||||
} from "@excalidraw/element/typeChecks";
|
||||
@ -133,6 +135,8 @@ import {
|
||||
} from "../scene";
|
||||
import { CaptureUpdateAction } from "../store";
|
||||
|
||||
import { toggleLinePolygonState } from "../../element/src/shapes";
|
||||
|
||||
import { register } from "./register";
|
||||
|
||||
import type { CaptureUpdateActionType } from "../store";
|
||||
@ -346,22 +350,50 @@ export const actionChangeBackgroundColor = register({
|
||||
name: "changeBackgroundColor",
|
||||
label: "labels.changeBackground",
|
||||
trackEvent: false,
|
||||
perform: (elements, appState, value) => {
|
||||
perform: (elements, appState, value, app) => {
|
||||
if (!value.currentItemBackgroundColor) {
|
||||
return {
|
||||
...(value.currentItemBackgroundColor && {
|
||||
elements: changeProperty(elements, appState, (el) =>
|
||||
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
||||
};
|
||||
}
|
||||
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
const shouldEnablePolygon = selectedElements.every((el) =>
|
||||
isLineElement(el),
|
||||
);
|
||||
|
||||
let nextElements;
|
||||
|
||||
if (
|
||||
shouldEnablePolygon &&
|
||||
value.currentItemBackgroundColor &&
|
||||
!isTransparent(value.currentItemBackgroundColor)
|
||||
) {
|
||||
const selectedElementsMap = arrayToMap(selectedElements);
|
||||
nextElements = elements.map((el) => {
|
||||
if (selectedElementsMap.has(el.id) && isLineElement(el)) {
|
||||
return newElementWith(el, {
|
||||
backgroundColor: value.currentItemBackgroundColor,
|
||||
...toggleLinePolygonState(el, true),
|
||||
});
|
||||
}
|
||||
return el;
|
||||
});
|
||||
} else {
|
||||
nextElements = changeProperty(elements, appState, (el) =>
|
||||
newElementWith(el, {
|
||||
backgroundColor: value.currentItemBackgroundColor,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
elements: nextElements,
|
||||
appState: {
|
||||
...appState,
|
||||
...value,
|
||||
},
|
||||
captureUpdate: !!value.currentItemBackgroundColor
|
||||
? CaptureUpdateAction.IMMEDIATELY
|
||||
: CaptureUpdateAction.EVENTUALLY,
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData, appProps }) => (
|
||||
|
Loading…
x
Reference in New Issue
Block a user