From 1739540f003e719e4f37c0ef299aabd806eecd25 Mon Sep 17 00:00:00 2001 From: Timur Khazamov Date: Thu, 9 Jan 2020 01:09:09 +0500 Subject: [PATCH] Creating a text near the center of a shape should put it in the center (#270) * Snap to element center * Fixed typo * Added comment * Reduced threshold to 30 * Skip snapping if alt key is pressed * Fixed creating text with shape tool --- src/index.tsx | 76 ++++++++++++++++++++++++++++++----- src/renderer/renderElement.ts | 1 - src/scene/comparisons.ts | 18 +++++++++ src/scene/index.ts | 7 +++- 4 files changed, 90 insertions(+), 12 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index a7bcb8dc..2648d721 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -26,7 +26,8 @@ import { hasBackground, hasStroke, getElementAtPosition, - createScene + createScene, + getElementContainingPosition } from "./scene"; import { renderScene } from "./renderer"; @@ -110,6 +111,7 @@ function addTextElement( const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5; const ELEMENT_TRANSLATE_AMOUNT = 1; +const TEXT_TO_CENTER_SNAP_THRESHOLD = 30; let lastCanvasWidth = -1; let lastCanvasHeight = -1; @@ -702,10 +704,25 @@ class App extends React.Component<{}, AppState> { } if (isTextElement(element)) { + let textX = e.clientX; + let textY = e.clientY; + if (!e.altKey) { + const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition( + x, + y + ); + if (snappedToCenterPosition) { + element.x = snappedToCenterPosition.elementCenterX; + element.y = snappedToCenterPosition.elementCenterY; + textX = snappedToCenterPosition.wysiwygX; + textY = snappedToCenterPosition.wysiwygY; + } + } + textWysiwyg({ initText: "", - x: e.clientX, - y: e.clientY, + x: textX, + y: textY, strokeColor: this.state.currentItemStrokeColor, font: this.state.currentItemFont, onSubmit: text => { @@ -932,12 +949,6 @@ class App extends React.Component<{}, AppState> { e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX; const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY; const elementAtPosition = getElementAtPosition(elements, x, y); - if (elementAtPosition && !isTextElement(elementAtPosition)) { - return; - } else if (elementAtPosition) { - elements.splice(elements.indexOf(elementAtPosition), 1); - this.forceUpdate(); - } const element = newElement( "text", @@ -954,7 +965,11 @@ class App extends React.Component<{}, AppState> { let initText = ""; let textX = e.clientX; let textY = e.clientY; - if (elementAtPosition) { + + if (elementAtPosition && isTextElement(elementAtPosition)) { + elements.splice(elements.indexOf(elementAtPosition), 1); + this.forceUpdate(); + Object.assign(element, elementAtPosition); // x and y will change after calling addTextElement function element.x = elementAtPosition.x + elementAtPosition.width / 2; @@ -970,6 +985,18 @@ class App extends React.Component<{}, AppState> { elementAtPosition.y + CANVAS_WINDOW_OFFSET_TOP + elementAtPosition.height / 2; + } else if (!e.altKey) { + const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition( + x, + y + ); + + if (snappedToCenterPosition) { + element.x = snappedToCenterPosition.elementCenterX; + element.y = snappedToCenterPosition.elementCenterY; + textX = snappedToCenterPosition.wysiwygX; + textY = snappedToCenterPosition.wysiwygY; + } } textWysiwyg({ @@ -1036,6 +1063,35 @@ class App extends React.Component<{}, AppState> { } }; + private getTextWysiwygSnappedToCenterPosition(x: number, y: number) { + const elementClickedInside = getElementContainingPosition(elements, x, y); + if (elementClickedInside) { + const elementCenterX = + elementClickedInside.x + elementClickedInside.width / 2; + const elementCenterY = + elementClickedInside.y + elementClickedInside.height / 2; + const distanceToCenter = Math.hypot( + x - elementCenterX, + y - elementCenterY + ); + const isSnappedToCenter = + distanceToCenter < TEXT_TO_CENTER_SNAP_THRESHOLD; + if (isSnappedToCenter) { + const wysiwygX = + this.state.scrollX + + elementClickedInside.x + + CANVAS_WINDOW_OFFSET_LEFT + + elementClickedInside.width / 2; + const wysiwygY = + this.state.scrollY + + elementClickedInside.y + + CANVAS_WINDOW_OFFSET_TOP + + elementClickedInside.height / 2; + return { wysiwygX, wysiwygY, elementCenterX, elementCenterY }; + } + } + } + componentDidUpdate() { renderScene(elements, rc, canvas, { scrollX: this.state.scrollX, diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index 8742e876..d40efafc 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -138,7 +138,6 @@ export function renderElement( context.fillStyle = fillStyle; context.font = font; context.globalAlpha = 1; - console.log(element); } else { throw new Error("Unimplemented type " + element.type); } diff --git a/src/scene/comparisons.ts b/src/scene/comparisons.ts index 0acc4a33..a1a3d41d 100644 --- a/src/scene/comparisons.ts +++ b/src/scene/comparisons.ts @@ -1,5 +1,6 @@ import { ExcalidrawElement } from "../element/types"; import { hitTest } from "../element/collision"; +import { getElementAbsoluteCoords } from "../element"; export const hasBackground = (elements: ExcalidrawElement[]) => elements.some( @@ -36,3 +37,20 @@ export function getElementAtPosition( return hitElement; } + +export function getElementContainingPosition( + elements: ExcalidrawElement[], + x: number, + y: number +) { + let hitElement = null; + // We need to to hit testing from front (end of the array) to back (beginning of the array) + for (let i = elements.length - 1; i >= 0; --i) { + const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[i]); + if (x1 < x && x < x2 && y1 < y && y < y2) { + hitElement = elements[i]; + break; + } + } + return hitElement; +} diff --git a/src/scene/index.ts b/src/scene/index.ts index 71e78d69..479c60ef 100644 --- a/src/scene/index.ts +++ b/src/scene/index.ts @@ -14,5 +14,10 @@ export { restoreFromLocalStorage, saveToLocalStorage } from "./data"; -export { hasBackground, hasStroke, getElementAtPosition } from "./comparisons"; +export { + hasBackground, + hasStroke, + getElementAtPosition, + getElementContainingPosition +} from "./comparisons"; export { createScene } from "./createScene";