simplify app and pointer down state

pull/8613/head
Ryan Di 5 months ago
parent 064bede0c5
commit 06a7a51baa

@ -5106,13 +5106,11 @@ class App extends React.Component<AppProps, AppState> {
private startImageCropping = (image: ExcalidrawImageElement) => { private startImageCropping = (image: ExcalidrawImageElement) => {
this.setState({ this.setState({
croppingElement: image, croppingElement: image,
isCropping: true,
}); });
}; };
private finishImageCropping = () => { private finishImageCropping = () => {
this.setState({ this.setState({
isCropping: false,
croppingElement: null, croppingElement: null,
}); });
}; };
@ -6590,12 +6588,6 @@ class App extends React.Component<AppProps, AppState> {
arrowDirection: "origin", arrowDirection: "origin",
center: { x: (maxX + minX) / 2, y: (maxY + minY) / 2 }, center: { x: (maxX + minX) / 2, y: (maxY + minY) / 2 },
}, },
crop: {
handleType: false,
isCropping: false,
offset: { x: 0, y: 0 },
complete: false,
},
hit: { hit: {
element: null, element: null,
allHitElements: [], allHitElements: [],
@ -6716,11 +6708,7 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState.resize.handleType = pointerDownState.resize.handleType =
elementWithTransformHandleType.transformHandleType; elementWithTransformHandleType.transformHandleType;
} else if (this.state.croppingElement) { } else if (this.state.croppingElement) {
this.setState({ pointerDownState.resize.handleType =
croppingElement:
elementWithTransformHandleType.element as ExcalidrawImageElement,
});
pointerDownState.crop.handleType =
elementWithTransformHandleType.transformHandleType; elementWithTransformHandleType.transformHandleType;
} else { } else {
this.setState({ this.setState({
@ -6761,17 +6749,6 @@ class App extends React.Component<AppProps, AppState> {
selectedElements[0], selectedElements[0],
); );
} }
} else if (pointerDownState.crop.handleType) {
pointerDownState.crop.isCropping = true;
pointerDownState.crop.offset = tupleToCoors(
getResizeOffsetXY(
pointerDownState.crop.handleType,
selectedElements,
this.scene.getNonDeletedElementsMap(),
pointerDownState.origin.x,
pointerDownState.origin.y,
),
);
} else { } else {
if (this.state.selectedLinearElement) { if (this.state.selectedLinearElement) {
const linearElementEditor = const linearElementEditor =
@ -6806,6 +6783,13 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState.origin.y, pointerDownState.origin.y,
); );
if (
this.state.croppingElement &&
pointerDownState.hit.element !== this.state.croppingElement
) {
this.finishImageCropping();
}
if (pointerDownState.hit.element) { if (pointerDownState.hit.element) {
// Early return if pointer is hitting link icon // Early return if pointer is hitting link icon
const hitLinkElement = this.getElementLinkAtPosition( const hitLinkElement = this.getElementLinkAtPosition(
@ -7667,14 +7651,10 @@ class App extends React.Component<AppProps, AppState> {
if (pointerDownState.resize.isResizing) { if (pointerDownState.resize.isResizing) {
pointerDownState.lastCoords.x = pointerCoords.x; pointerDownState.lastCoords.x = pointerCoords.x;
pointerDownState.lastCoords.y = pointerCoords.y; pointerDownState.lastCoords.y = pointerCoords.y;
if (this.maybeHandleResize(pointerDownState, event)) { if (this.maybeHandleCrop(pointerDownState, event)) {
return true; return true;
} }
} if (this.maybeHandleResize(pointerDownState, event)) {
if (pointerDownState.crop.isCropping) {
pointerDownState.lastCoords.x = pointerCoords.x;
pointerDownState.lastCoords.y = pointerCoords.y;
if (this.maybeHandleCrop(pointerDownState, event)) {
return true; return true;
} }
} }
@ -7847,17 +7827,17 @@ class App extends React.Component<AppProps, AppState> {
} }
} }
// #region drag // #region move crop region
const croppingElement = this.state.croppingElement;
if ( if (
selectedElements.length === 1 && croppingElement &&
isImageElement(selectedElements[0]) && croppingElement.crop !== null &&
this.state.croppingElement?.id === selectedElements[0].id && pointerDownState.hit.element === croppingElement
selectedElements[0].crop !== null
) { ) {
const crop = selectedElements[0].crop; const crop = croppingElement.crop;
const image = const image =
isInitializedImageElement(selectedElements[0]) && isInitializedImageElement(croppingElement) &&
this.imageCache.get(selectedElements[0].fileId)?.image; this.imageCache.get(croppingElement.fileId)?.image;
if (image && !(image instanceof Promise)) { if (image && !(image instanceof Promise)) {
const instantDragOffset = { const instantDragOffset = {
@ -7865,42 +7845,21 @@ class App extends React.Component<AppProps, AppState> {
y: pointerCoords.y - lastPointerCoords.y, y: pointerCoords.y - lastPointerCoords.y,
}; };
// current offset is based on the element's width and height
const uncroppedWidth =
selectedElements[0].initialWidth *
selectedElements[0].resizeFactors[0];
const uncroppedHeight =
selectedElements[0].initialHeight *
selectedElements[0].resizeFactors[1];
const SENSITIVITY_FACTOR = 3;
const adjustedOffset = {
x:
instantDragOffset.x *
(uncroppedWidth / image.naturalWidth) *
SENSITIVITY_FACTOR,
y:
instantDragOffset.y *
(uncroppedHeight / image.naturalHeight) *
SENSITIVITY_FACTOR,
};
const nextCrop = { const nextCrop = {
...crop, ...crop,
x: clamp( x: clamp(
crop.x - adjustedOffset.x, crop.x - instantDragOffset.x,
0, 0,
image.naturalWidth - crop.width, image.naturalWidth - crop.width,
), ),
y: clamp( y: clamp(
crop.y - adjustedOffset.y, crop.y - instantDragOffset.y,
0, 0,
image.naturalHeight - crop.height, image.naturalHeight - crop.height,
), ),
}; };
mutateElement(selectedElements[0], { mutateElement(croppingElement, {
crop: nextCrop, crop: nextCrop,
}); });
} }
@ -8290,6 +8249,7 @@ class App extends React.Component<AppProps, AppState> {
const { const {
newElement, newElement,
resizingElement, resizingElement,
croppingElement,
multiElement, multiElement,
activeTool, activeTool,
isResizing, isResizing,
@ -8300,6 +8260,7 @@ class App extends React.Component<AppProps, AppState> {
this.setState((prevState) => ({ this.setState((prevState) => ({
isResizing: false, isResizing: false,
isRotating: false, isRotating: false,
isCropping: false,
resizingElement: null, resizingElement: null,
selectionElement: null, selectionElement: null,
frameToHighlight: null, frameToHighlight: null,
@ -8793,11 +8754,16 @@ class App extends React.Component<AppProps, AppState> {
} }
} }
// click outside the cropping region to exit o0ol
if ( if (
isCropping && // not in the cropping mode at all
!isResizing && !croppingElement ||
((!hitElement && !pointerDownState.crop.isCropping) || // in the cropping mode
(hitElement && hitElement !== this.state.croppingElement)) (croppingElement &&
// not cropping and no hit element
((!hitElement && !isCropping) ||
// hitting something else
(hitElement && hitElement !== croppingElement)))
) { ) {
this.finishImageCropping(); this.finishImageCropping();
} }
@ -10033,37 +9999,33 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState: PointerDownState, pointerDownState: PointerDownState,
event: MouseEvent | KeyboardEvent, event: MouseEvent | KeyboardEvent,
): boolean => { ): boolean => {
if (pointerDownState.crop.complete) { // to crop, we must already be in the cropping mode, where croppingElement has been set
return true; if (!this.state.croppingElement) {
}
const selectedElements = getSelectedElements(
this.scene.getNonDeletedElements(),
this.state,
);
if (selectedElements.length > 1) {
// don't see much sense in allowing multi-crop, that would be weird
return false; return false;
} }
const transformHandleType = pointerDownState.crop.handleType; const transformHandleType = pointerDownState.resize.handleType;
const pointerCoords = pointerDownState.lastCoords; const pointerCoords = pointerDownState.lastCoords;
const [x, y] = getGridPoint( const [x, y] = getGridPoint(
pointerCoords.x - pointerDownState.crop.offset.x, pointerCoords.x - pointerDownState.resize.offset.x,
pointerCoords.y - pointerDownState.crop.offset.y, pointerCoords.y - pointerDownState.resize.offset.y,
this.getEffectiveGridSize(), this.getEffectiveGridSize(),
); );
const elementToCrop = selectedElements[0] as ExcalidrawImageElement;
if (transformHandleType) { if (transformHandleType) {
cropElement( cropElement(
elementToCrop, this.state.croppingElement,
this.scene.getNonDeletedElementsMap(), this.scene.getNonDeletedElementsMap(),
this.imageCache, this.imageCache,
transformHandleType, transformHandleType,
x, x,
y, y,
); );
this.setState({
isCropping: transformHandleType && transformHandleType !== "rotation",
});
return true; return true;
} }

@ -107,7 +107,6 @@ const getRelevantAppStateProps = (
frameToHighlight: appState.frameToHighlight, frameToHighlight: appState.frameToHighlight,
editingGroupId: appState.editingGroupId, editingGroupId: appState.editingGroupId,
currentHoveredFontFamily: appState.currentHoveredFontFamily, currentHoveredFontFamily: appState.currentHoveredFontFamily,
isCropping: appState.isCropping,
croppingElement: appState.croppingElement, croppingElement: appState.croppingElement,
}); });

@ -1,4 +1,4 @@
import { Point } from "points-on-curve"; import { type Point } from "points-on-curve";
import { import {
type Radians, type Radians,
point, point,
@ -15,7 +15,7 @@ import {
import { updateBoundElements } from "./binding"; import { updateBoundElements } from "./binding";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
import { TransformHandleType } from "./transformHandles"; import { TransformHandleType } from "./transformHandles";
import { import type {
ElementsMap, ElementsMap,
ExcalidrawElement, ExcalidrawElement,
ExcalidrawImageElement, ExcalidrawImageElement,

@ -602,7 +602,7 @@ const renderCropHandles = (
const lineWidth = 3 / appState.zoom.value; const lineWidth = 3 / appState.zoom.value;
const length = 15 / appState.zoom.value; const length = 15 / appState.zoom.value;
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords( const [x1, y1, , , cx, cy] = getElementAbsoluteCoords(
croppingElement, croppingElement,
elementsMap, elementsMap,
); );
@ -1002,7 +1002,7 @@ const _renderInteractiveScene = ({
); );
} }
if (appState.isCropping && appState.croppingElement) { if (appState.croppingElement && !appState.isCropping) {
renderCropHandles( renderCropHandles(
context, context,
renderConfig, renderConfig,

@ -177,7 +177,6 @@ export type StaticCanvasAppState = Readonly<
frameRendering: AppState["frameRendering"]; frameRendering: AppState["frameRendering"];
currentHoveredFontFamily: AppState["currentHoveredFontFamily"]; currentHoveredFontFamily: AppState["currentHoveredFontFamily"];
// Cropping // Cropping
isCropping: AppState["isCropping"];
croppingElement: AppState["croppingElement"]; croppingElement: AppState["croppingElement"];
} }
>; >;
@ -677,12 +676,6 @@ export type PointerDownState = Readonly<{
// This is a center point of selected elements determined on the initial pointer down event (for rotation only) // This is a center point of selected elements determined on the initial pointer down event (for rotation only)
center: { x: number; y: number }; center: { x: number; y: number };
}; };
crop: {
handleType: MaybeTransformHandleType;
isCropping: boolean;
offset: { x: number; y: number };
complete: boolean;
};
hit: { hit: {
// The element the pointer is "hitting", is determined on the initial // The element the pointer is "hitting", is determined on the initial
// pointer down event // pointer down event

Loading…
Cancel
Save