mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: show a mid point for linear elements (#5534)
* feat: Add a mid point for linear elements * fix tests * show mid point only on hover * hacky fix :( * don't add mid points if present and only add outside editor * improve styling and always show phantom point instead of just on hover * fix tests * fix * only add polyfill for test * add hover state for phantom point * fix tests * fix * Add Array.at polyfill * reuse `centerPoint()` helper * reuse `distance2d` * use `Point` type Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
731093f631
commit
5a8dbe8030
10 changed files with 266 additions and 43 deletions
|
@ -11,6 +11,7 @@ import {
|
|||
isPathALoop,
|
||||
getGridPoint,
|
||||
rotatePoint,
|
||||
centerPoint,
|
||||
} from "../math";
|
||||
import { getElementAbsoluteCoords, getLockedLinearCursorAlignSize } from ".";
|
||||
import { getElementPointsCoords } from "./bounds";
|
||||
|
@ -51,6 +52,7 @@ export class LinearElementEditor {
|
|||
| "keep";
|
||||
public readonly endBindingElement: ExcalidrawBindableElement | null | "keep";
|
||||
public readonly hoverPointIndex: number;
|
||||
public readonly midPointHovered: boolean;
|
||||
|
||||
constructor(element: NonDeleted<ExcalidrawLinearElement>, scene: Scene) {
|
||||
this.elementId = element.id as string & {
|
||||
|
@ -70,6 +72,7 @@ export class LinearElementEditor {
|
|||
lastClickedPoint: -1,
|
||||
};
|
||||
this.hoverPointIndex = -1;
|
||||
this.midPointHovered = false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -157,7 +160,6 @@ export class LinearElementEditor {
|
|||
if (!linearElementEditor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { selectedPointsIndices, elementId } = linearElementEditor;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
if (!element) {
|
||||
|
@ -357,6 +359,59 @@ export class LinearElementEditor {
|
|||
};
|
||||
}
|
||||
|
||||
static isHittingMidPoint = (
|
||||
linearElementEditor: LinearElementEditor,
|
||||
scenePointer: { x: number; y: number },
|
||||
appState: AppState,
|
||||
) => {
|
||||
if (appState.editingLinearElement) {
|
||||
return false;
|
||||
}
|
||||
const { elementId } = linearElementEditor;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||
element,
|
||||
appState.zoom,
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
);
|
||||
if (clickedPointIndex >= 0) {
|
||||
return false;
|
||||
}
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
||||
if (points.length >= 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const midPoint = this.getMidPoint(linearElementEditor);
|
||||
if (midPoint) {
|
||||
const threshold =
|
||||
LinearElementEditor.POINT_HANDLE_SIZE / appState.zoom.value;
|
||||
const distance = distance2d(
|
||||
midPoint[0],
|
||||
midPoint[1],
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
);
|
||||
return distance <= threshold;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
static getMidPoint(linearElementEditor: LinearElementEditor) {
|
||||
const { elementId } = linearElementEditor;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
||||
|
||||
return centerPoint(points[0], points.at(-1)!);
|
||||
}
|
||||
|
||||
static handlePointerDown(
|
||||
event: React.PointerEvent<HTMLCanvasElement>,
|
||||
appState: AppState,
|
||||
|
@ -367,11 +422,13 @@ export class LinearElementEditor {
|
|||
didAddPoint: boolean;
|
||||
hitElement: NonDeleted<ExcalidrawElement> | null;
|
||||
linearElementEditor: LinearElementEditor | null;
|
||||
isMidPoint: boolean;
|
||||
} {
|
||||
const ret: ReturnType<typeof LinearElementEditor["handlePointerDown"]> = {
|
||||
didAddPoint: false,
|
||||
hitElement: null,
|
||||
linearElementEditor: null,
|
||||
isMidPoint: false,
|
||||
};
|
||||
|
||||
if (!linearElementEditor) {
|
||||
|
@ -384,6 +441,45 @@ export class LinearElementEditor {
|
|||
if (!element) {
|
||||
return ret;
|
||||
}
|
||||
const hittingMidPoint = LinearElementEditor.isHittingMidPoint(
|
||||
linearElementEditor,
|
||||
scenePointer,
|
||||
appState,
|
||||
);
|
||||
if (
|
||||
LinearElementEditor.isHittingMidPoint(
|
||||
linearElementEditor,
|
||||
scenePointer,
|
||||
appState,
|
||||
)
|
||||
) {
|
||||
const midPoint = this.getMidPoint(linearElementEditor);
|
||||
if (midPoint) {
|
||||
mutateElement(element, {
|
||||
points: [
|
||||
element.points[0],
|
||||
LinearElementEditor.createPointAt(
|
||||
element,
|
||||
midPoint[0],
|
||||
midPoint[1],
|
||||
appState.gridSize,
|
||||
),
|
||||
...element.points.slice(1),
|
||||
],
|
||||
});
|
||||
}
|
||||
ret.didAddPoint = true;
|
||||
ret.isMidPoint = true;
|
||||
ret.linearElementEditor = {
|
||||
...linearElementEditor,
|
||||
selectedPointsIndices: element.points[1],
|
||||
pointerDownState: {
|
||||
prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices,
|
||||
lastClickedPoint: -1,
|
||||
},
|
||||
lastUncommittedPoint: null,
|
||||
};
|
||||
}
|
||||
if (event.altKey && appState.editingLinearElement) {
|
||||
if (linearElementEditor.lastUncommittedPoint == null) {
|
||||
mutateElement(element, {
|
||||
|
@ -397,6 +493,7 @@ export class LinearElementEditor {
|
|||
),
|
||||
],
|
||||
});
|
||||
ret.didAddPoint = true;
|
||||
}
|
||||
history.resumeRecording();
|
||||
ret.linearElementEditor = {
|
||||
|
@ -426,7 +523,7 @@ export class LinearElementEditor {
|
|||
|
||||
// if we clicked on a point, set the element as hitElement otherwise
|
||||
// it would get deselected if the point is outside the hitbox area
|
||||
if (clickedPointIndex > -1) {
|
||||
if (clickedPointIndex >= 0 || hittingMidPoint) {
|
||||
ret.hitElement = element;
|
||||
} else {
|
||||
// You might be wandering why we are storing the binding elements on
|
||||
|
@ -567,14 +664,14 @@ export class LinearElementEditor {
|
|||
/** scene coords */
|
||||
static getPointsGlobalCoordinates(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
) {
|
||||
): Point[] {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
return element.points.map((point) => {
|
||||
let { x, y } = element;
|
||||
[x, y] = rotate(x + point[0], y + point[1], cx, cy, element.angle);
|
||||
return [x, y];
|
||||
return [x, y] as const;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue