mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-14 09:45:27 +01:00
feat: resize elements from the sides (#7855)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
@@ -90,6 +90,7 @@ import {
|
||||
EDITOR_LS_KEYS,
|
||||
isIOS,
|
||||
supportsResizeObserver,
|
||||
DEFAULT_COLLISION_THRESHOLD,
|
||||
} from "../constants";
|
||||
import { ExportedElements, exportCanvas, loadFromBlob } from "../data";
|
||||
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||
@@ -1703,6 +1704,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
scale={window.devicePixelRatio}
|
||||
appState={this.state}
|
||||
device={this.device}
|
||||
renderInteractiveSceneCallback={
|
||||
this.renderInteractiveSceneCallback
|
||||
}
|
||||
@@ -4528,7 +4530,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
shape: this.getElementShape(elementWithHighestZIndex),
|
||||
// when overlapping, we would like to be more precise
|
||||
// this also avoids the need to update past tests
|
||||
threshold: this.getHitThreshold() / 2,
|
||||
threshold: this.getElementHitThreshold() / 2,
|
||||
frameNameBound: isFrameLikeElement(elementWithHighestZIndex)
|
||||
? this.frameNameBoundsCache.get(elementWithHighestZIndex)
|
||||
: null,
|
||||
@@ -4591,8 +4593,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
return elements;
|
||||
}
|
||||
|
||||
private getHitThreshold() {
|
||||
return 10 / this.state.zoom.value;
|
||||
private getElementHitThreshold() {
|
||||
return DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value;
|
||||
}
|
||||
|
||||
private hitElement(
|
||||
@@ -4610,7 +4612,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const selectionShape = getSelectionBoxShape(
|
||||
element,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.getHitThreshold(),
|
||||
this.getElementHitThreshold(),
|
||||
);
|
||||
|
||||
return isPointInShape([x, y], selectionShape);
|
||||
@@ -4631,7 +4633,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
y,
|
||||
element,
|
||||
shape: this.getElementShape(element),
|
||||
threshold: this.getHitThreshold(),
|
||||
threshold: this.getElementHitThreshold(),
|
||||
frameNameBound: isFrameLikeElement(element)
|
||||
? this.frameNameBoundsCache.get(element)
|
||||
: null,
|
||||
@@ -4663,7 +4665,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
y,
|
||||
element: elements[index],
|
||||
shape: this.getElementShape(elements[index]),
|
||||
threshold: this.getHitThreshold(),
|
||||
threshold: this.getElementHitThreshold(),
|
||||
})
|
||||
) {
|
||||
hitElement = elements[index];
|
||||
@@ -4916,7 +4918,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
y: sceneY,
|
||||
element: container,
|
||||
shape: this.getElementShape(container),
|
||||
threshold: this.getHitThreshold(),
|
||||
threshold: this.getElementHitThreshold(),
|
||||
})
|
||||
) {
|
||||
const midPoint = getContainerCenter(
|
||||
@@ -5331,24 +5333,41 @@ class App extends React.Component<AppProps, AppState> {
|
||||
!isOverScrollBar &&
|
||||
!this.state.editingLinearElement
|
||||
) {
|
||||
const elementWithTransformHandleType = getElementWithTransformHandleType(
|
||||
elements,
|
||||
this.state,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
this.state.zoom,
|
||||
event.pointerType,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (
|
||||
elementWithTransformHandleType &&
|
||||
elementWithTransformHandleType.transformHandleType
|
||||
) {
|
||||
setCursor(
|
||||
this.interactiveCanvas,
|
||||
getCursorForResizingElement(elementWithTransformHandleType),
|
||||
// for linear elements, we'd like to prioritize point dragging over edge resizing
|
||||
// therefore, we update and check hovered point index first
|
||||
if (this.state.selectedLinearElement) {
|
||||
this.handleHoverSelectedLinearElement(
|
||||
this.state.selectedLinearElement,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!this.state.selectedLinearElement ||
|
||||
this.state.selectedLinearElement.hoverPointIndex === -1
|
||||
) {
|
||||
const elementWithTransformHandleType =
|
||||
getElementWithTransformHandleType(
|
||||
elements,
|
||||
this.state,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
this.state.zoom,
|
||||
event.pointerType,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.device,
|
||||
);
|
||||
if (
|
||||
elementWithTransformHandleType &&
|
||||
elementWithTransformHandleType.transformHandleType
|
||||
) {
|
||||
setCursor(
|
||||
this.interactiveCanvas,
|
||||
getCursorForResizingElement(elementWithTransformHandleType),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (selectedElements.length > 1 && !isOverScrollBar) {
|
||||
const transformHandleType = getTransformHandleTypeFromCoords(
|
||||
@@ -5357,6 +5376,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
scenePointerY,
|
||||
this.state.zoom,
|
||||
event.pointerType,
|
||||
this.device,
|
||||
);
|
||||
if (transformHandleType) {
|
||||
setCursor(
|
||||
@@ -5509,7 +5529,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
);
|
||||
const threshold = this.getHitThreshold();
|
||||
const threshold = this.getElementHitThreshold();
|
||||
const point = { ...pointerDownState.lastCoords };
|
||||
let samplingInterval = 0;
|
||||
while (samplingInterval <= distance) {
|
||||
@@ -5606,7 +5626,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||
} else {
|
||||
} else if (this.hitElement(scenePointerX, scenePointerY, element)) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
|
||||
}
|
||||
} else if (this.hitElement(scenePointerX, scenePointerY, element)) {
|
||||
@@ -6306,7 +6326,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
|
||||
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
||||
if (
|
||||
selectedElements.length === 1 &&
|
||||
!this.state.editingLinearElement &&
|
||||
!(
|
||||
this.state.selectedLinearElement &&
|
||||
this.state.selectedLinearElement.hoverPointIndex !== -1
|
||||
)
|
||||
) {
|
||||
const elementWithTransformHandleType =
|
||||
getElementWithTransformHandleType(
|
||||
elements,
|
||||
@@ -6316,6 +6343,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.state.zoom,
|
||||
event.pointerType,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.device,
|
||||
);
|
||||
if (elementWithTransformHandleType != null) {
|
||||
this.setState({
|
||||
@@ -6331,6 +6359,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState.origin.y,
|
||||
this.state.zoom,
|
||||
event.pointerType,
|
||||
this.device,
|
||||
);
|
||||
}
|
||||
if (pointerDownState.resize.handleType) {
|
||||
@@ -6587,7 +6616,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
|
||||
// How many pixels off the shape boundary we still consider a hit
|
||||
const threshold = this.getHitThreshold();
|
||||
const threshold = this.getElementHitThreshold();
|
||||
const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
|
||||
return (
|
||||
point.x > x1 - threshold &&
|
||||
@@ -8412,7 +8441,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
y: pointerDownState.origin.y,
|
||||
element: hitElement,
|
||||
shape: this.getElementShape(hitElement),
|
||||
threshold: this.getHitThreshold(),
|
||||
threshold: this.getElementHitThreshold(),
|
||||
frameNameBound: isFrameLikeElement(hitElement)
|
||||
? this.frameNameBoundsCache.get(hitElement)
|
||||
: null,
|
||||
@@ -9525,7 +9554,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.scene.getElementsMapIncludingDeleted(),
|
||||
shouldRotateWithDiscreteAngle(event),
|
||||
shouldResizeFromCenter(event),
|
||||
selectedElements.length === 1 && isImageElement(selectedElements[0])
|
||||
selectedElements.some((element) => isImageElement(element))
|
||||
? !shouldMaintainAspectRatio(event)
|
||||
: shouldMaintainAspectRatio(event),
|
||||
resizeX,
|
||||
|
||||
Reference in New Issue
Block a user