fix: show bounding box for 3 or more linear point elements (#5554)
* fix: show bounding box for 3+ linear point elements * refactor * show bounding box for 3 points as well * fix dragging bounding box for linear elements * Increase margin/padding for linear elements * fix cursor and keep bounding box same but offset resize handles * introduce slight padding for selection border * better * add constant for spacing
This commit is contained in:
parent
fe56975f19
commit
731093f631
@ -261,6 +261,7 @@ import {
|
||||
isPointHittingLinkIcon,
|
||||
isLocalLink,
|
||||
} from "../element/Hyperlink";
|
||||
import { shouldShowBoundingBox } from "../element/transformHandles";
|
||||
|
||||
const deviceContextInitialValue = {
|
||||
isSmScreen: false,
|
||||
@ -3046,6 +3047,16 @@ class App extends React.Component<AppProps, AppState> {
|
||||
} else {
|
||||
setCursor(this.canvas, CURSOR_TYPE.MOVE);
|
||||
}
|
||||
} else if (
|
||||
shouldShowBoundingBox([element]) &&
|
||||
isHittingElementBoundingBoxWithoutHittingElement(
|
||||
element,
|
||||
this.state,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
)
|
||||
) {
|
||||
setCursor(this.canvas, CURSOR_TYPE.MOVE);
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -35,6 +35,7 @@ import { getShapeForElement } from "../renderer/renderElement";
|
||||
import { hasBoundTextElement, isImageElement } from "./typeChecks";
|
||||
import { isTextElement } from ".";
|
||||
import { isTransparent } from "../utils";
|
||||
import { shouldShowBoundingBox } from "./transformHandles";
|
||||
|
||||
const isElementDraggableFromInside = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
@ -64,7 +65,10 @@ export const hitTest = (
|
||||
const threshold = 10 / appState.zoom.value;
|
||||
const point: Point = [x, y];
|
||||
|
||||
if (isElementSelected(appState, element) && !appState.selectedLinearElement) {
|
||||
if (
|
||||
isElementSelected(appState, element) &&
|
||||
shouldShowBoundingBox([element])
|
||||
) {
|
||||
return isPointHittingElementBoundingBox(element, point, threshold);
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { ExcalidrawElement, PointerType } from "./types";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
PointerType,
|
||||
} from "./types";
|
||||
|
||||
import { getElementAbsoluteCoords, Bounds } from "./bounds";
|
||||
import { rotate } from "../math";
|
||||
import { Zoom } from "../types";
|
||||
import { isTextElement } from ".";
|
||||
import { isLinearElement } from "./typeChecks";
|
||||
import { DEFAULT_SPACING } from "../renderer/renderScene";
|
||||
|
||||
export type TransformHandleDirection =
|
||||
| "n"
|
||||
@ -81,6 +86,7 @@ export const getTransformHandlesFromCoords = (
|
||||
zoom: Zoom,
|
||||
pointerType: PointerType,
|
||||
omitSides: { [T in TransformHandleType]?: boolean } = {},
|
||||
margin = 4,
|
||||
): TransformHandles => {
|
||||
const size = transformHandleSizes[pointerType];
|
||||
const handleWidth = size / zoom.value;
|
||||
@ -93,9 +99,7 @@ export const getTransformHandlesFromCoords = (
|
||||
const height = y2 - y1;
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
|
||||
const dashedLineMargin = 4 / zoom.value;
|
||||
|
||||
const dashedLineMargin = margin / zoom.value;
|
||||
const centeringOffset = (size - 8) / (2 * zoom.value);
|
||||
|
||||
const transformHandles: TransformHandles = {
|
||||
@ -248,12 +252,29 @@ export const getTransformHandles = (
|
||||
} else if (isTextElement(element)) {
|
||||
omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT;
|
||||
}
|
||||
|
||||
const dashedLineMargin = isLinearElement(element)
|
||||
? DEFAULT_SPACING * 3
|
||||
: DEFAULT_SPACING;
|
||||
return getTransformHandlesFromCoords(
|
||||
getElementAbsoluteCoords(element),
|
||||
element.angle,
|
||||
zoom,
|
||||
pointerType,
|
||||
omitSides,
|
||||
dashedLineMargin,
|
||||
);
|
||||
};
|
||||
|
||||
export const shouldShowBoundingBox = (
|
||||
elements: NonDeletedExcalidrawElement[],
|
||||
) => {
|
||||
if (elements.length > 1) {
|
||||
return true;
|
||||
}
|
||||
const element = elements[0];
|
||||
if (!isLinearElement(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return element.points.length > 2;
|
||||
};
|
||||
|
@ -44,6 +44,7 @@ import {
|
||||
isBindingEnabled,
|
||||
} from "../element/binding";
|
||||
import {
|
||||
shouldShowBoundingBox,
|
||||
TransformHandles,
|
||||
TransformHandleType,
|
||||
} from "../element/transformHandles";
|
||||
@ -61,6 +62,7 @@ import {
|
||||
import { isLinearElement } from "../element/typeChecks";
|
||||
|
||||
const hasEmojiSupport = supportsEmoji();
|
||||
export const DEFAULT_SPACING = 4;
|
||||
|
||||
const strokeRectWithRotation = (
|
||||
context: CanvasRenderingContext2D,
|
||||
@ -219,6 +221,7 @@ const renderLinearElementPointHighlight = (
|
||||
|
||||
context.restore();
|
||||
};
|
||||
|
||||
export const _renderScene = (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
appState: AppState,
|
||||
@ -346,7 +349,6 @@ export const _renderScene = (
|
||||
) {
|
||||
renderLinearElementPointHighlight(context, appState, renderConfig);
|
||||
}
|
||||
|
||||
// Paint selected elements
|
||||
if (
|
||||
renderSelection &&
|
||||
@ -354,6 +356,8 @@ export const _renderScene = (
|
||||
!appState.editingLinearElement
|
||||
) {
|
||||
const locallySelectedElements = getSelectedElements(elements, appState);
|
||||
const showBoundingBox = shouldShowBoundingBox(locallySelectedElements);
|
||||
|
||||
const locallySelectedIds = locallySelectedElements.map(
|
||||
(element) => element.id,
|
||||
);
|
||||
@ -373,9 +377,8 @@ export const _renderScene = (
|
||||
renderConfig,
|
||||
locallySelectedElements[0] as ExcalidrawLinearElement,
|
||||
);
|
||||
// render bounding box
|
||||
// (unless dragging a single linear element)
|
||||
} else if (!appState.draggingElement || !isSingleLinearElementSelected) {
|
||||
}
|
||||
if (showBoundingBox) {
|
||||
const selections = elements.reduce((acc, element) => {
|
||||
const selectionColors = [];
|
||||
// local user
|
||||
@ -434,12 +437,18 @@ export const _renderScene = (
|
||||
addSelectionForGroupId(appState.editingGroupId);
|
||||
}
|
||||
selections.forEach((selection) =>
|
||||
renderSelectionBorder(context, renderConfig, selection),
|
||||
renderSelectionBorder(
|
||||
context,
|
||||
renderConfig,
|
||||
selection,
|
||||
isSingleLinearElementSelected ? DEFAULT_SPACING * 2 : DEFAULT_SPACING,
|
||||
),
|
||||
);
|
||||
}
|
||||
// Paint resize transformHandles
|
||||
context.save();
|
||||
context.translate(renderConfig.scrollX, renderConfig.scrollY);
|
||||
|
||||
if (locallySelectedElements.length === 1) {
|
||||
context.fillStyle = oc.white;
|
||||
const transformHandles = getTransformHandles(
|
||||
@ -447,10 +456,7 @@ export const _renderScene = (
|
||||
renderConfig.zoom,
|
||||
"mouse", // when we render we don't know which pointer type so use mouse
|
||||
);
|
||||
if (
|
||||
!appState.viewModeEnabled &&
|
||||
!isLinearElement(locallySelectedElements[0])
|
||||
) {
|
||||
if (!appState.viewModeEnabled && showBoundingBox) {
|
||||
renderTransformHandles(
|
||||
context,
|
||||
renderConfig,
|
||||
@ -714,24 +720,21 @@ const renderTransformHandles = (
|
||||
Object.keys(transformHandles).forEach((key) => {
|
||||
const transformHandle = transformHandles[key as TransformHandleType];
|
||||
if (transformHandle !== undefined) {
|
||||
const [x, y, width, height] = transformHandle;
|
||||
|
||||
context.save();
|
||||
context.lineWidth = 1 / renderConfig.zoom.value;
|
||||
if (key === "rotation") {
|
||||
fillCircle(
|
||||
context,
|
||||
transformHandle[0] + transformHandle[2] / 2,
|
||||
transformHandle[1] + transformHandle[3] / 2,
|
||||
transformHandle[2] / 2,
|
||||
);
|
||||
fillCircle(context, x + width / 2, y + height / 2, width / 2);
|
||||
} else {
|
||||
strokeRectWithRotation(
|
||||
context,
|
||||
transformHandle[0],
|
||||
transformHandle[1],
|
||||
transformHandle[2],
|
||||
transformHandle[3],
|
||||
transformHandle[0] + transformHandle[2] / 2,
|
||||
transformHandle[1] + transformHandle[3] / 2,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
x + width / 2,
|
||||
y + height / 2,
|
||||
angle,
|
||||
true, // fill before stroke
|
||||
);
|
||||
@ -752,13 +755,14 @@ const renderSelectionBorder = (
|
||||
elementY2: number;
|
||||
selectionColors: string[];
|
||||
},
|
||||
padding = 4,
|
||||
) => {
|
||||
const { angle, elementX1, elementY1, elementX2, elementY2, selectionColors } =
|
||||
elementProperties;
|
||||
const elementWidth = elementX2 - elementX1;
|
||||
const elementHeight = elementY2 - elementY1;
|
||||
|
||||
const dashedLinePadding = 4 / renderConfig.zoom.value;
|
||||
const dashedLinePadding = padding / renderConfig.zoom.value;
|
||||
const dashWidth = 8 / renderConfig.zoom.value;
|
||||
const spaceWidth = 4 / renderConfig.zoom.value;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user