feat: resize text element (#1650)
* feat: resize text element * ignore small font size change that leads jankiness Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
5327e8a3dc
commit
7edcea9a93
@ -21,6 +21,33 @@ export const OMIT_SIDES_FOR_MULTIPLE_ELEMENTS = {
|
|||||||
rotation: true,
|
rotation: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const OMIT_SIDES_FOR_TEXT_ELEMENT = {
|
||||||
|
e: true,
|
||||||
|
s: true,
|
||||||
|
n: true,
|
||||||
|
w: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const OMIT_SIDES_FOR_LINE_SLASH = {
|
||||||
|
e: true,
|
||||||
|
s: true,
|
||||||
|
n: true,
|
||||||
|
w: true,
|
||||||
|
nw: true,
|
||||||
|
se: true,
|
||||||
|
rotation: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const OMIT_SIDES_FOR_LINE_BACKSLASH = {
|
||||||
|
e: true,
|
||||||
|
s: true,
|
||||||
|
n: true,
|
||||||
|
w: true,
|
||||||
|
ne: true,
|
||||||
|
sw: true,
|
||||||
|
rotation: true,
|
||||||
|
};
|
||||||
|
|
||||||
const generateHandler = (
|
const generateHandler = (
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@ -180,13 +207,7 @@ export const handlerRectangles = (
|
|||||||
zoom: number,
|
zoom: number,
|
||||||
pointerType: PointerType = "mouse",
|
pointerType: PointerType = "mouse",
|
||||||
) => {
|
) => {
|
||||||
const handlers = handlerRectanglesFromCoords(
|
let omitSides: { [T in Sides]?: boolean } = {};
|
||||||
getElementAbsoluteCoords(element),
|
|
||||||
element.angle,
|
|
||||||
zoom,
|
|
||||||
pointerType,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
element.type === "arrow" ||
|
element.type === "arrow" ||
|
||||||
element.type === "line" ||
|
element.type === "line" ||
|
||||||
@ -195,43 +216,27 @@ export const handlerRectangles = (
|
|||||||
if (element.points.length === 2) {
|
if (element.points.length === 2) {
|
||||||
// only check the last point because starting point is always (0,0)
|
// only check the last point because starting point is always (0,0)
|
||||||
const [, p1] = element.points;
|
const [, p1] = element.points;
|
||||||
|
|
||||||
if (p1[0] === 0 || p1[1] === 0) {
|
if (p1[0] === 0 || p1[1] === 0) {
|
||||||
return {
|
omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH;
|
||||||
nw: handlers.nw,
|
} else if (p1[0] > 0 && p1[1] < 0) {
|
||||||
se: handlers.se,
|
omitSides = OMIT_SIDES_FOR_LINE_SLASH;
|
||||||
} as typeof handlers;
|
} else if (p1[0] > 0 && p1[1] > 0) {
|
||||||
|
omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH;
|
||||||
|
} else if (p1[0] < 0 && p1[1] > 0) {
|
||||||
|
omitSides = OMIT_SIDES_FOR_LINE_SLASH;
|
||||||
|
} else if (p1[0] < 0 && p1[1] < 0) {
|
||||||
|
omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (element.type === "text") {
|
||||||
|
omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p1[0] > 0 && p1[1] < 0) {
|
return handlerRectanglesFromCoords(
|
||||||
return {
|
getElementAbsoluteCoords(element),
|
||||||
ne: handlers.ne,
|
element.angle,
|
||||||
sw: handlers.sw,
|
zoom,
|
||||||
} as typeof handlers;
|
pointerType,
|
||||||
}
|
omitSides,
|
||||||
|
);
|
||||||
if (p1[0] > 0 && p1[1] > 0) {
|
|
||||||
return {
|
|
||||||
nw: handlers.nw,
|
|
||||||
se: handlers.se,
|
|
||||||
} as typeof handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p1[0] < 0 && p1[1] > 0) {
|
|
||||||
return {
|
|
||||||
ne: handlers.ne,
|
|
||||||
sw: handlers.sw,
|
|
||||||
} as typeof handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p1[0] < 0 && p1[1] < 0) {
|
|
||||||
return {
|
|
||||||
nw: handlers.nw,
|
|
||||||
se: handlers.se,
|
|
||||||
} as typeof handlers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handlers;
|
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ import { rescalePoints } from "../points";
|
|||||||
import { rotate, adjustXYWithRotation, getFlipAdjustment } from "../math";
|
import { rotate, adjustXYWithRotation, getFlipAdjustment } from "../math";
|
||||||
import {
|
import {
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawTextElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@ -24,6 +25,7 @@ import {
|
|||||||
getResizeCenterPointKey,
|
getResizeCenterPointKey,
|
||||||
getResizeWithSidesSameLengthKey,
|
getResizeWithSidesSameLengthKey,
|
||||||
} from "../keys";
|
} from "../keys";
|
||||||
|
import { measureText, getFontString } from "../utils";
|
||||||
|
|
||||||
type ResizeTestType = ReturnType<typeof resizeTest>;
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
|
|
||||||
@ -55,6 +57,20 @@ export const resizeElements = (
|
|||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
);
|
);
|
||||||
|
} else if (
|
||||||
|
element.type === "text" &&
|
||||||
|
(resizeHandle === "nw" ||
|
||||||
|
resizeHandle === "ne" ||
|
||||||
|
resizeHandle === "sw" ||
|
||||||
|
resizeHandle === "se")
|
||||||
|
) {
|
||||||
|
resizeSingleTextElement(
|
||||||
|
element,
|
||||||
|
resizeHandle,
|
||||||
|
getResizeCenterPointKey(event),
|
||||||
|
pointerX,
|
||||||
|
pointerY,
|
||||||
|
);
|
||||||
} else if (resizeHandle) {
|
} else if (resizeHandle) {
|
||||||
resizeSingleElement(
|
resizeSingleElement(
|
||||||
element,
|
element,
|
||||||
@ -188,6 +204,95 @@ const rescalePointsInElement = (
|
|||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
const resizeSingleTextElement = (
|
||||||
|
element: NonDeleted<ExcalidrawTextElement>,
|
||||||
|
resizeHandle: "nw" | "ne" | "sw" | "se",
|
||||||
|
isResizeFromCenter: boolean,
|
||||||
|
pointerX: number,
|
||||||
|
pointerY: number,
|
||||||
|
) => {
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
|
const cx = (x1 + x2) / 2;
|
||||||
|
const cy = (y1 + y2) / 2;
|
||||||
|
// rotation pointer with reverse angle
|
||||||
|
const [rotatedX, rotatedY] = rotate(
|
||||||
|
pointerX,
|
||||||
|
pointerY,
|
||||||
|
cx,
|
||||||
|
cy,
|
||||||
|
-element.angle,
|
||||||
|
);
|
||||||
|
let scale;
|
||||||
|
switch (resizeHandle) {
|
||||||
|
case "se":
|
||||||
|
scale = Math.max(
|
||||||
|
(rotatedX - x1) / (x2 - x1),
|
||||||
|
(rotatedY - y1) / (y2 - y1),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "nw":
|
||||||
|
scale = Math.max(
|
||||||
|
(x2 - rotatedX) / (x2 - x1),
|
||||||
|
(y2 - rotatedY) / (y2 - y1),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "ne":
|
||||||
|
scale = Math.max(
|
||||||
|
(rotatedX - x1) / (x2 - x1),
|
||||||
|
(y2 - rotatedY) / (y2 - y1),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "sw":
|
||||||
|
scale = Math.max(
|
||||||
|
(x2 - rotatedX) / (x2 - x1),
|
||||||
|
(rotatedY - y1) / (y2 - y1),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (scale > 0) {
|
||||||
|
const newFontSize = Math.max(element.fontSize * scale, 10);
|
||||||
|
const metrics = measureText(
|
||||||
|
element.text,
|
||||||
|
getFontString({ fontSize: newFontSize, fontFamily: element.fontFamily }),
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
Math.abs(metrics.width - element.width) <= 1 ||
|
||||||
|
Math.abs(metrics.height - element.height) <= 1
|
||||||
|
) {
|
||||||
|
// we ignore 1px change to avoid janky behavior
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
metrics.width,
|
||||||
|
metrics.height,
|
||||||
|
);
|
||||||
|
const deltaX1 = (x1 - nextX1) / 2;
|
||||||
|
const deltaY1 = (y1 - nextY1) / 2;
|
||||||
|
const deltaX2 = (x2 - nextX2) / 2;
|
||||||
|
const deltaY2 = (y2 - nextY2) / 2;
|
||||||
|
const [nextElementX, nextElementY] = adjustXYWithRotation(
|
||||||
|
resizeHandle,
|
||||||
|
element.x,
|
||||||
|
element.y,
|
||||||
|
element.angle,
|
||||||
|
deltaX1,
|
||||||
|
deltaY1,
|
||||||
|
deltaX2,
|
||||||
|
deltaY2,
|
||||||
|
isResizeFromCenter,
|
||||||
|
);
|
||||||
|
mutateElement(element, {
|
||||||
|
fontSize: newFontSize,
|
||||||
|
width: metrics.width,
|
||||||
|
height: metrics.height,
|
||||||
|
baseline: metrics.baseline,
|
||||||
|
x: nextElementX,
|
||||||
|
y: nextElementY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const resizeSingleElement = (
|
const resizeSingleElement = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
|
resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
|
||||||
|
@ -45,11 +45,6 @@ export const resizeTest = (
|
|||||||
return "rotation" as HandlerRectanglesRet;
|
return "rotation" as HandlerRectanglesRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.type === "text") {
|
|
||||||
// can't resize text elements
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filter = Object.keys(handlers).filter((key) => {
|
const filter = Object.keys(handlers).filter((key) => {
|
||||||
const handler = handlers[key as Exclude<HandlerRectanglesRet, "rotation">]!;
|
const handler = handlers[key as Exclude<HandlerRectanglesRet, "rotation">]!;
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
|
@ -304,7 +304,7 @@ export const renderScene = (
|
|||||||
handler[2],
|
handler[2],
|
||||||
handler[3],
|
handler[3],
|
||||||
);
|
);
|
||||||
} else if (locallySelectedElements[0].type !== "text") {
|
} else {
|
||||||
strokeRectWithRotation(
|
strokeRectWithRotation(
|
||||||
context,
|
context,
|
||||||
handler[0],
|
handler[0],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user