From 731093f6315e02f9dc6428dcf422e301a1922ec6 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 10 Aug 2022 21:42:28 +0530 Subject: [PATCH] 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 --- src/components/App.tsx | 11 ++++++++ src/element/collision.ts | 6 ++++- src/element/transformHandles.ts | 31 +++++++++++++++++---- src/renderer/renderScene.ts | 48 ++++++++++++++++++--------------- 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 59278c69..9b34442e 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -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 { } else { setCursor(this.canvas, CURSOR_TYPE.MOVE); } + } else if ( + shouldShowBoundingBox([element]) && + isHittingElementBoundingBoxWithoutHittingElement( + element, + this.state, + scenePointerX, + scenePointerY, + ) + ) { + setCursor(this.canvas, CURSOR_TYPE.MOVE); } if ( diff --git a/src/element/collision.ts b/src/element/collision.ts index 901d7a09..45d5d609 100644 --- a/src/element/collision.ts +++ b/src/element/collision.ts @@ -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); } diff --git a/src/element/transformHandles.ts b/src/element/transformHandles.ts index 1d20e2c6..baf7f279 100644 --- a/src/element/transformHandles.ts +++ b/src/element/transformHandles.ts @@ -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; +}; diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 8cabc00f..4eee17bc 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -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;