resize elements from center point (#1225)
* add hint & support multi-line hints * resize from center point using the new resize maths * resize with origin element when lifting alt key * add readonly to elementOriginPosition * add setResizeWithCenterKeyLifted * isResizeFromCenter logic * offsetX and offsetY * simplify equations * creating element from center point * lint * lint * lint * remove revert on key up logic Co-authored-by: dwelle <luzar.david@gmail.com> Co-authored-by: daishi <daishi@axlight.com>
This commit is contained in:
parent
8c49770e3b
commit
ed6fb60337
@ -71,7 +71,12 @@ import {
|
|||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
setCursorForShape,
|
setCursorForShape,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import { KEYS, isArrowKey } from "../keys";
|
import {
|
||||||
|
KEYS,
|
||||||
|
isArrowKey,
|
||||||
|
getResizeCenterPointKey,
|
||||||
|
getResizeWithSidesSameLengthKey,
|
||||||
|
} from "../keys";
|
||||||
|
|
||||||
import { findShapeByKey, shapesShortcutKeys } from "../shapes";
|
import { findShapeByKey, shapesShortcutKeys } from "../shapes";
|
||||||
import { createHistory, SceneHistory } from "../history";
|
import { createHistory, SceneHistory } from "../history";
|
||||||
@ -1801,6 +1806,7 @@ class App extends React.Component<any, AppState> {
|
|||||||
let draggingOccurred = false;
|
let draggingOccurred = false;
|
||||||
let hitElement: ExcalidrawElement | null = null;
|
let hitElement: ExcalidrawElement | null = null;
|
||||||
let hitElementWasAddedToSelection = false;
|
let hitElementWasAddedToSelection = false;
|
||||||
|
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
const elements = globalSceneState.getElements();
|
const elements = globalSceneState.getElements();
|
||||||
const selectedElements = getSelectedElements(elements, this.state);
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
@ -2021,7 +2027,7 @@ class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let resizeArrowFn: ResizeArrowFnType | null = null;
|
let resizeArrowFn: ResizeArrowFnType | null = null;
|
||||||
const setResizeArrrowFn = (fn: ResizeArrowFnType) => {
|
const setResizeArrowFn = (fn: ResizeArrowFnType) => {
|
||||||
resizeArrowFn = fn;
|
resizeArrowFn = fn;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2082,7 +2088,7 @@ class App extends React.Component<any, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
this.setAppState,
|
this.setAppState,
|
||||||
resizeArrowFn,
|
resizeArrowFn,
|
||||||
setResizeArrrowFn,
|
setResizeArrowFn,
|
||||||
event,
|
event,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
@ -2189,7 +2195,7 @@ class App extends React.Component<any, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (event.shiftKey) {
|
if (getResizeWithSidesSameLengthKey(event)) {
|
||||||
({ width, height } = getPerfectElementSize(
|
({ width, height } = getPerfectElementSize(
|
||||||
this.state.elementType,
|
this.state.elementType,
|
||||||
width,
|
width,
|
||||||
@ -2201,9 +2207,19 @@ class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newX = x < originX ? originX - width : originX;
|
||||||
|
let newY = y < originY ? originY - height : originY;
|
||||||
|
|
||||||
|
if (getResizeCenterPointKey(event)) {
|
||||||
|
width += width;
|
||||||
|
height += height;
|
||||||
|
newX = originX - width / 2;
|
||||||
|
newY = originY - height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
mutateElement(draggingElement, {
|
mutateElement(draggingElement, {
|
||||||
x: x < originX ? originX - width : originX,
|
x: newX,
|
||||||
y: y < originY ? originY - height : originY,
|
y: newY,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
});
|
});
|
||||||
|
@ -8,11 +8,12 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 54px;
|
top: 54px;
|
||||||
transform: translateX(calc(-50% - 16px)); /* 16px is half of lock icon */
|
transform: translateX(calc(-50% - 16px)); /* 16px is half of lock icon */
|
||||||
|
white-space: pre;
|
||||||
|
text-align: center;
|
||||||
@media #{$media-query} {
|
@media #{$media-query} {
|
||||||
position: static;
|
position: static;
|
||||||
transform: none;
|
transform: none;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
|
@ -19,6 +19,10 @@ import {
|
|||||||
getCursorForResizingElement,
|
getCursorForResizingElement,
|
||||||
normalizeResizeHandle,
|
normalizeResizeHandle,
|
||||||
} from "./resizeTest";
|
} from "./resizeTest";
|
||||||
|
import {
|
||||||
|
getResizeCenterPointKey,
|
||||||
|
getResizeWithSidesSameLengthKey,
|
||||||
|
} from "../keys";
|
||||||
|
|
||||||
type ResizeTestType = ReturnType<typeof resizeTest>;
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
|
|
||||||
@ -117,13 +121,13 @@ export const resizeElements = (
|
|||||||
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
setAppState: (obj: any) => void,
|
setAppState: (obj: any) => void,
|
||||||
resizeArrowFn: ResizeArrowFnType | null,
|
resizeArrowFn: ResizeArrowFnType | null, // XXX eliminate in #1339
|
||||||
setResizeArrowFn: (fn: ResizeArrowFnType) => void,
|
setResizeArrowFn: (fn: ResizeArrowFnType) => void, // XXX eliminate in #1339
|
||||||
event: PointerEvent,
|
event: PointerEvent, // XXX we want to make it independent?
|
||||||
xPointer: number,
|
xPointer: number,
|
||||||
yPointer: number,
|
yPointer: number,
|
||||||
lastX: number,
|
lastX: number, // XXX eliminate in #1339
|
||||||
lastY: number,
|
lastY: number, // XXX eliminate in #1339
|
||||||
) => {
|
) => {
|
||||||
setAppState({
|
setAppState({
|
||||||
isResizing: resizeHandle !== "rotation",
|
isResizing: resizeHandle !== "rotation",
|
||||||
@ -191,7 +195,8 @@ export const resizeElements = (
|
|||||||
xPointer,
|
xPointer,
|
||||||
yPointer,
|
yPointer,
|
||||||
offsetPointer,
|
offsetPointer,
|
||||||
event.shiftKey,
|
getResizeWithSidesSameLengthKey(event),
|
||||||
|
getResizeCenterPointKey(event),
|
||||||
);
|
);
|
||||||
if (resized.width !== 0 && resized.height !== 0) {
|
if (resized.width !== 0 && resized.height !== 0) {
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
|
@ -14,6 +14,7 @@ export const KEYS = {
|
|||||||
SPACE: " ",
|
SPACE: " ",
|
||||||
QUESTION_MARK: "?",
|
QUESTION_MARK: "?",
|
||||||
F_KEY_CODE: 70,
|
F_KEY_CODE: 70,
|
||||||
|
ALT_KEY_CODE: 18,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type Key = keyof typeof KEYS;
|
export type Key = keyof typeof KEYS;
|
||||||
@ -26,3 +27,8 @@ export function isArrowKey(keyCode: string) {
|
|||||||
keyCode === KEYS.ARROW_UP
|
keyCode === KEYS.ARROW_UP
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getResizeCenterPointKey = (event: MouseEvent | KeyboardEvent) =>
|
||||||
|
event.altKey || event.which === KEYS.ALT_KEY_CODE;
|
||||||
|
export const getResizeWithSidesSameLengthKey = (event: MouseEvent) =>
|
||||||
|
event.shiftKey;
|
||||||
|
@ -109,7 +109,7 @@
|
|||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "Click to start multiple points, drag for single line",
|
"linearElement": "Click to start multiple points, drag for single line",
|
||||||
"linearElementMulti": "Click on last point or press Escape or Enter to finish",
|
"linearElementMulti": "Click on last point or press Escape or Enter to finish",
|
||||||
"resize": "You can constrain proportions by holding SHIFT while resizing",
|
"resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center",
|
||||||
"rotate": "You can constrain angles by holding SHIFT while rotating"
|
"rotate": "You can constrain angles by holding SHIFT while rotating"
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
|
35
src/math.ts
35
src/math.ts
@ -63,26 +63,43 @@ const adjustXYWithRotation = (
|
|||||||
angle: number,
|
angle: number,
|
||||||
deltaX: number,
|
deltaX: number,
|
||||||
deltaY: number,
|
deltaY: number,
|
||||||
|
isResizeFromCenter: boolean,
|
||||||
) => {
|
) => {
|
||||||
const cos = Math.cos(angle);
|
const cos = Math.cos(angle);
|
||||||
const sin = Math.sin(angle);
|
const sin = Math.sin(angle);
|
||||||
deltaX /= 2;
|
deltaX /= 2;
|
||||||
deltaY /= 2;
|
deltaY /= 2;
|
||||||
if (side === "e" || side === "ne" || side === "se") {
|
if (side === "e" || side === "ne" || side === "se") {
|
||||||
x += deltaX * (1 - cos);
|
if (isResizeFromCenter) {
|
||||||
y += deltaX * -sin;
|
x += deltaX;
|
||||||
|
} else {
|
||||||
|
x += deltaX * (1 - cos);
|
||||||
|
y += deltaX * -sin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (side === "s" || side === "sw" || side === "se") {
|
if (side === "s" || side === "sw" || side === "se") {
|
||||||
x += deltaY * sin;
|
if (isResizeFromCenter) {
|
||||||
y += deltaY * (1 - cos);
|
y += deltaY;
|
||||||
|
} else {
|
||||||
|
x += deltaY * sin;
|
||||||
|
y += deltaY * (1 - cos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (side === "w" || side === "nw" || side === "sw") {
|
if (side === "w" || side === "nw" || side === "sw") {
|
||||||
x += deltaX * (1 + cos);
|
if (isResizeFromCenter) {
|
||||||
y += deltaX * sin;
|
x += deltaX;
|
||||||
|
} else {
|
||||||
|
x += deltaX * (1 + cos);
|
||||||
|
y += deltaX * sin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (side === "n" || side === "nw" || side === "ne") {
|
if (side === "n" || side === "nw" || side === "ne") {
|
||||||
x += deltaY * -sin;
|
if (isResizeFromCenter) {
|
||||||
y += deltaY * (1 + cos);
|
y += deltaY;
|
||||||
|
} else {
|
||||||
|
x += deltaY * -sin;
|
||||||
|
y += deltaY * (1 + cos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return { x, y };
|
return { x, y };
|
||||||
};
|
};
|
||||||
@ -100,6 +117,7 @@ export const resizeXYWidthHightWithRotation = (
|
|||||||
yPointer: number,
|
yPointer: number,
|
||||||
offsetPointer: number,
|
offsetPointer: number,
|
||||||
sidesWithSameLength: boolean,
|
sidesWithSameLength: boolean,
|
||||||
|
isResizeFromCenter: boolean,
|
||||||
) => {
|
) => {
|
||||||
// center point for rotation
|
// center point for rotation
|
||||||
const cx = x + width / 2;
|
const cx = x + width / 2;
|
||||||
@ -139,6 +157,7 @@ export const resizeXYWidthHightWithRotation = (
|
|||||||
angle,
|
angle,
|
||||||
width - nextWidth,
|
width - nextWidth,
|
||||||
height - nextHeight,
|
height - nextHeight,
|
||||||
|
isResizeFromCenter,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user