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, isPointHittingLinkIcon,
isLocalLink, isLocalLink,
} from "../element/Hyperlink"; } from "../element/Hyperlink";
import { shouldShowBoundingBox } from "../element/transformHandles";
const deviceContextInitialValue = { const deviceContextInitialValue = {
isSmScreen: false, isSmScreen: false,
@ -3046,6 +3047,16 @@ class App extends React.Component<AppProps, AppState> {
} else { } else {
setCursor(this.canvas, CURSOR_TYPE.MOVE); setCursor(this.canvas, CURSOR_TYPE.MOVE);
} }
} else if (
shouldShowBoundingBox([element]) &&
isHittingElementBoundingBoxWithoutHittingElement(
element,
this.state,
scenePointerX,
scenePointerY,
)
) {
setCursor(this.canvas, CURSOR_TYPE.MOVE);
} }
if ( if (

View File

@ -35,6 +35,7 @@ import { getShapeForElement } from "../renderer/renderElement";
import { hasBoundTextElement, isImageElement } from "./typeChecks"; import { hasBoundTextElement, isImageElement } from "./typeChecks";
import { isTextElement } from "."; import { isTextElement } from ".";
import { isTransparent } from "../utils"; import { isTransparent } from "../utils";
import { shouldShowBoundingBox } from "./transformHandles";
const isElementDraggableFromInside = ( const isElementDraggableFromInside = (
element: NonDeletedExcalidrawElement, element: NonDeletedExcalidrawElement,
@ -64,7 +65,10 @@ export const hitTest = (
const threshold = 10 / appState.zoom.value; const threshold = 10 / appState.zoom.value;
const point: Point = [x, y]; const point: Point = [x, y];
if (isElementSelected(appState, element) && !appState.selectedLinearElement) { if (
isElementSelected(appState, element) &&
shouldShowBoundingBox([element])
) {
return isPointHittingElementBoundingBox(element, point, threshold); 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 { getElementAbsoluteCoords, Bounds } from "./bounds";
import { rotate } from "../math"; import { rotate } from "../math";
import { Zoom } from "../types"; import { Zoom } from "../types";
import { isTextElement } from "."; import { isTextElement } from ".";
import { isLinearElement } from "./typeChecks"; import { isLinearElement } from "./typeChecks";
import { DEFAULT_SPACING } from "../renderer/renderScene";
export type TransformHandleDirection = export type TransformHandleDirection =
| "n" | "n"
@ -81,6 +86,7 @@ export const getTransformHandlesFromCoords = (
zoom: Zoom, zoom: Zoom,
pointerType: PointerType, pointerType: PointerType,
omitSides: { [T in TransformHandleType]?: boolean } = {}, omitSides: { [T in TransformHandleType]?: boolean } = {},
margin = 4,
): TransformHandles => { ): TransformHandles => {
const size = transformHandleSizes[pointerType]; const size = transformHandleSizes[pointerType];
const handleWidth = size / zoom.value; const handleWidth = size / zoom.value;
@ -93,9 +99,7 @@ export const getTransformHandlesFromCoords = (
const height = y2 - y1; const height = y2 - y1;
const cx = (x1 + x2) / 2; const cx = (x1 + x2) / 2;
const cy = (y1 + y2) / 2; const cy = (y1 + y2) / 2;
const dashedLineMargin = margin / zoom.value;
const dashedLineMargin = 4 / zoom.value;
const centeringOffset = (size - 8) / (2 * zoom.value); const centeringOffset = (size - 8) / (2 * zoom.value);
const transformHandles: TransformHandles = { const transformHandles: TransformHandles = {
@ -248,12 +252,29 @@ export const getTransformHandles = (
} else if (isTextElement(element)) { } else if (isTextElement(element)) {
omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT; omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT;
} }
const dashedLineMargin = isLinearElement(element)
? DEFAULT_SPACING * 3
: DEFAULT_SPACING;
return getTransformHandlesFromCoords( return getTransformHandlesFromCoords(
getElementAbsoluteCoords(element), getElementAbsoluteCoords(element),
element.angle, element.angle,
zoom, zoom,
pointerType, pointerType,
omitSides, 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, isBindingEnabled,
} from "../element/binding"; } from "../element/binding";
import { import {
shouldShowBoundingBox,
TransformHandles, TransformHandles,
TransformHandleType, TransformHandleType,
} from "../element/transformHandles"; } from "../element/transformHandles";
@ -61,6 +62,7 @@ import {
import { isLinearElement } from "../element/typeChecks"; import { isLinearElement } from "../element/typeChecks";
const hasEmojiSupport = supportsEmoji(); const hasEmojiSupport = supportsEmoji();
export const DEFAULT_SPACING = 4;
const strokeRectWithRotation = ( const strokeRectWithRotation = (
context: CanvasRenderingContext2D, context: CanvasRenderingContext2D,
@ -219,6 +221,7 @@ const renderLinearElementPointHighlight = (
context.restore(); context.restore();
}; };
export const _renderScene = ( export const _renderScene = (
elements: readonly NonDeletedExcalidrawElement[], elements: readonly NonDeletedExcalidrawElement[],
appState: AppState, appState: AppState,
@ -346,7 +349,6 @@ export const _renderScene = (
) { ) {
renderLinearElementPointHighlight(context, appState, renderConfig); renderLinearElementPointHighlight(context, appState, renderConfig);
} }
// Paint selected elements // Paint selected elements
if ( if (
renderSelection && renderSelection &&
@ -354,6 +356,8 @@ export const _renderScene = (
!appState.editingLinearElement !appState.editingLinearElement
) { ) {
const locallySelectedElements = getSelectedElements(elements, appState); const locallySelectedElements = getSelectedElements(elements, appState);
const showBoundingBox = shouldShowBoundingBox(locallySelectedElements);
const locallySelectedIds = locallySelectedElements.map( const locallySelectedIds = locallySelectedElements.map(
(element) => element.id, (element) => element.id,
); );
@ -373,9 +377,8 @@ export const _renderScene = (
renderConfig, renderConfig,
locallySelectedElements[0] as ExcalidrawLinearElement, locallySelectedElements[0] as ExcalidrawLinearElement,
); );
// render bounding box }
// (unless dragging a single linear element) if (showBoundingBox) {
} else if (!appState.draggingElement || !isSingleLinearElementSelected) {
const selections = elements.reduce((acc, element) => { const selections = elements.reduce((acc, element) => {
const selectionColors = []; const selectionColors = [];
// local user // local user
@ -434,12 +437,18 @@ export const _renderScene = (
addSelectionForGroupId(appState.editingGroupId); addSelectionForGroupId(appState.editingGroupId);
} }
selections.forEach((selection) => selections.forEach((selection) =>
renderSelectionBorder(context, renderConfig, selection), renderSelectionBorder(
context,
renderConfig,
selection,
isSingleLinearElementSelected ? DEFAULT_SPACING * 2 : DEFAULT_SPACING,
),
); );
} }
// Paint resize transformHandles // Paint resize transformHandles
context.save(); context.save();
context.translate(renderConfig.scrollX, renderConfig.scrollY); context.translate(renderConfig.scrollX, renderConfig.scrollY);
if (locallySelectedElements.length === 1) { if (locallySelectedElements.length === 1) {
context.fillStyle = oc.white; context.fillStyle = oc.white;
const transformHandles = getTransformHandles( const transformHandles = getTransformHandles(
@ -447,10 +456,7 @@ export const _renderScene = (
renderConfig.zoom, renderConfig.zoom,
"mouse", // when we render we don't know which pointer type so use mouse "mouse", // when we render we don't know which pointer type so use mouse
); );
if ( if (!appState.viewModeEnabled && showBoundingBox) {
!appState.viewModeEnabled &&
!isLinearElement(locallySelectedElements[0])
) {
renderTransformHandles( renderTransformHandles(
context, context,
renderConfig, renderConfig,
@ -714,24 +720,21 @@ const renderTransformHandles = (
Object.keys(transformHandles).forEach((key) => { Object.keys(transformHandles).forEach((key) => {
const transformHandle = transformHandles[key as TransformHandleType]; const transformHandle = transformHandles[key as TransformHandleType];
if (transformHandle !== undefined) { if (transformHandle !== undefined) {
const [x, y, width, height] = transformHandle;
context.save(); context.save();
context.lineWidth = 1 / renderConfig.zoom.value; context.lineWidth = 1 / renderConfig.zoom.value;
if (key === "rotation") { if (key === "rotation") {
fillCircle( fillCircle(context, x + width / 2, y + height / 2, width / 2);
context,
transformHandle[0] + transformHandle[2] / 2,
transformHandle[1] + transformHandle[3] / 2,
transformHandle[2] / 2,
);
} else { } else {
strokeRectWithRotation( strokeRectWithRotation(
context, context,
transformHandle[0], x,
transformHandle[1], y,
transformHandle[2], width,
transformHandle[3], height,
transformHandle[0] + transformHandle[2] / 2, x + width / 2,
transformHandle[1] + transformHandle[3] / 2, y + height / 2,
angle, angle,
true, // fill before stroke true, // fill before stroke
); );
@ -752,13 +755,14 @@ const renderSelectionBorder = (
elementY2: number; elementY2: number;
selectionColors: string[]; selectionColors: string[];
}, },
padding = 4,
) => { ) => {
const { angle, elementX1, elementY1, elementX2, elementY2, selectionColors } = const { angle, elementX1, elementY1, elementX2, elementY2, selectionColors } =
elementProperties; elementProperties;
const elementWidth = elementX2 - elementX1; const elementWidth = elementX2 - elementX1;
const elementHeight = elementY2 - elementY1; const elementHeight = elementY2 - elementY1;
const dashedLinePadding = 4 / renderConfig.zoom.value; const dashedLinePadding = padding / renderConfig.zoom.value;
const dashWidth = 8 / renderConfig.zoom.value; const dashWidth = 8 / renderConfig.zoom.value;
const spaceWidth = 4 / renderConfig.zoom.value; const spaceWidth = 4 / renderConfig.zoom.value;