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:
Aakansha Doshi 2022-08-10 21:42:28 +05:30 committed by GitHub
parent fe56975f19
commit 731093f631
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 68 additions and 28 deletions

View File

@ -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 (

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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;