do not center text when not applicable (#1783)

This commit is contained in:
David Luzar 2020-06-25 21:21:27 +02:00 committed by GitHub
parent 9c89504b6f
commit cd87bd6901
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 418 additions and 321 deletions

View file

@ -27,6 +27,7 @@ import {
getResizeArrowDirection,
getResizeHandlerFromCoords,
isNonDeletedElement,
updateTextElement,
dragSelectedElements,
getDragOffsetXY,
dragNewElement,
@ -55,7 +56,11 @@ import Portal from "./Portal";
import { renderScene } from "../renderer";
import { AppState, GestureEvent, Gesture } from "../types";
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
import {
ExcalidrawElement,
ExcalidrawTextElement,
NonDeleted,
} from "../element/types";
import { distance2d, isPathALoop, getGridPoint } from "../math";
@ -113,6 +118,7 @@ import {
EVENT,
ENV,
CANVAS_ONLY_ACTIONS,
DEFAULT_VERTICAL_ALIGN,
GRID_SIZE,
} from "../constants";
import {
@ -583,7 +589,11 @@ class App extends React.Component<any, AppState> {
if (scrollBars) {
currentScrollBars = scrollBars;
}
const scrolledOutside = !atLeastOneVisibleElement && elements.length > 0;
const scrolledOutside =
// hide when editing text
this.state.editingElement?.type === "text"
? false
: !atLeastOneVisibleElement && elements.length > 0;
if (this.state.scrolledOutside !== scrolledOutside) {
this.setState({ scrolledOutside: scrolledOutside });
}
@ -790,6 +800,7 @@ class App extends React.Component<any, AppState> {
fontSize: this.state.currentItemFontSize,
fontFamily: this.state.currentItemFontFamily,
textAlign: this.state.currentItemTextAlign,
verticalAlign: DEFAULT_VERTICAL_ALIGN,
});
globalSceneState.replaceAllElements([
@ -1250,12 +1261,9 @@ class App extends React.Component<any, AppState> {
!isLinearElement(selectedElements[0])
) {
const selectedElement = selectedElements[0];
const x = selectedElement.x + selectedElement.width / 2;
const y = selectedElement.y + selectedElement.height / 2;
this.startTextEditing({
x: x,
y: y,
sceneX: selectedElement.x + selectedElement.width / 2,
sceneY: selectedElement.y + selectedElement.height / 2,
});
event.preventDefault();
return;
@ -1346,10 +1354,10 @@ class App extends React.Component<any, AppState> {
private handleTextWysiwyg(
element: ExcalidrawTextElement,
{
x,
y,
isExistingElement = false,
}: { x: number; y: number; isExistingElement?: boolean },
}: {
isExistingElement?: boolean;
},
) {
const resetSelection = () => {
this.setState({
@ -1358,26 +1366,13 @@ class App extends React.Component<any, AppState> {
});
};
const deleteElement = () => {
globalSceneState.replaceAllElements([
...globalSceneState.getElementsIncludingDeleted().map((_element) => {
if (_element.id === element.id) {
return newElementWith(_element, { isDeleted: true });
}
return _element;
}),
]);
};
const updateElement = (text: string) => {
globalSceneState.replaceAllElements([
...globalSceneState.getElementsIncludingDeleted().map((_element) => {
if (_element.id === element.id) {
return newTextElement({
...(_element as ExcalidrawTextElement),
x: element.x,
y: element.y,
if (_element.id === element.id && isTextElement(_element)) {
return updateTextElement(_element, {
text,
isDeleted: !text.trim(),
});
}
return _element;
@ -1387,22 +1382,18 @@ class App extends React.Component<any, AppState> {
textWysiwyg({
id: element.id,
x,
y,
initText: element.text,
strokeColor: element.strokeColor,
opacity: element.opacity,
fontSize: element.fontSize,
fontFamily: element.fontFamily,
angle: element.angle,
textAlign: element.textAlign,
zoom: this.state.zoom,
getViewportCoords: (x, y) => {
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
{ sceneX: x, sceneY: y },
this.state,
this.canvas,
window.devicePixelRatio,
);
return [viewportX, viewportY];
},
onChange: withBatchedUpdates((text) => {
if (text) {
updateElement(text);
} else {
deleteElement();
}
updateElement(text);
}),
onSubmit: withBatchedUpdates((text) => {
updateElement(text);
@ -1419,7 +1410,7 @@ class App extends React.Component<any, AppState> {
resetSelection();
}),
onCancel: withBatchedUpdates(() => {
deleteElement();
updateElement("");
if (isExistingElement) {
history.resumeRecording();
}
@ -1438,20 +1429,11 @@ class App extends React.Component<any, AppState> {
updateElement(element.text);
}
private startTextEditing = ({
x,
y,
clientX,
clientY,
centerIfPossible = true,
}: {
x: number;
y: number;
clientX?: number;
clientY?: number;
centerIfPossible?: boolean;
}) => {
const elementAtPosition = getElementAtPosition(
private getTextElementAtPosition(
x: number,
y: number,
): NonDeleted<ExcalidrawTextElement> | null {
const element = getElementAtPosition(
globalSceneState.getElements(),
this.state,
x,
@ -1459,78 +1441,83 @@ class App extends React.Component<any, AppState> {
this.state.zoom,
);
const element =
elementAtPosition && isTextElement(elementAtPosition)
? elementAtPosition
: newTextElement({
x: x,
y: y,
strokeColor: this.state.currentItemStrokeColor,
backgroundColor: this.state.currentItemBackgroundColor,
fillStyle: this.state.currentItemFillStyle,
strokeWidth: this.state.currentItemStrokeWidth,
strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity,
text: "",
fontSize: this.state.currentItemFontSize,
fontFamily: this.state.currentItemFontFamily,
textAlign: this.state.currentItemTextAlign,
});
if (element && isTextElement(element) && !element.isDeleted) {
return element;
}
return null;
}
this.setState({ editingElement: element });
private startTextEditing = ({
sceneX,
sceneY,
insertAtParentCenter = true,
}: {
/** X position to insert text at */
sceneX: number;
/** Y position to insert text at */
sceneY: number;
/** whether to attempt to insert at element center if applicable */
insertAtParentCenter?: boolean;
}) => {
const existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
let textX = clientX || x;
let textY = clientY || y;
let isExistingTextElement = false;
if (elementAtPosition && isTextElement(elementAtPosition)) {
isExistingTextElement = true;
const centerElementX = elementAtPosition.x + elementAtPosition.width / 2;
const centerElementY = elementAtPosition.y + elementAtPosition.height / 2;
const {
x: centerElementXInViewport,
y: centerElementYInViewport,
} = sceneCoordsToViewportCoords(
{ sceneX: centerElementX, sceneY: centerElementY },
const parentCenterPosition =
insertAtParentCenter &&
this.getTextWysiwygSnappedToCenterPosition(
sceneX,
sceneY,
this.state,
this.canvas,
window.devicePixelRatio,
);
textX = centerElementXInViewport;
textY = centerElementYInViewport;
const element = existingTextElement
? existingTextElement
: newTextElement({
x: parentCenterPosition
? parentCenterPosition.elementCenterX
: sceneX,
y: parentCenterPosition
? parentCenterPosition.elementCenterY
: sceneY,
strokeColor: this.state.currentItemStrokeColor,
backgroundColor: this.state.currentItemBackgroundColor,
fillStyle: this.state.currentItemFillStyle,
strokeWidth: this.state.currentItemStrokeWidth,
strokeStyle: this.state.currentItemStrokeStyle,
roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity,
text: "",
fontSize: this.state.currentItemFontSize,
fontFamily: this.state.currentItemFontFamily,
textAlign: parentCenterPosition
? "center"
: this.state.currentItemTextAlign,
verticalAlign: parentCenterPosition
? "middle"
: DEFAULT_VERTICAL_ALIGN,
});
// x and y will change after calling newTextElement function
mutateElement(element, {
x: centerElementX,
y: centerElementY,
});
this.setState({ editingElement: element });
if (existingTextElement) {
// if text element is no longer centered to a container, reset
// verticalAlign to default because it's currently internal-only
if (!parentCenterPosition || element.textAlign !== "center") {
mutateElement(element, { verticalAlign: DEFAULT_VERTICAL_ALIGN });
}
} else {
globalSceneState.replaceAllElements([
...globalSceneState.getElementsIncludingDeleted(),
element,
]);
if (centerIfPossible) {
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
x,
y,
this.state,
this.canvas,
window.devicePixelRatio,
);
if (snappedToCenterPosition) {
mutateElement(element, {
x: snappedToCenterPosition.elementCenterX,
y: snappedToCenterPosition.elementCenterY,
});
textX = snappedToCenterPosition.wysiwygX;
textY = snappedToCenterPosition.wysiwygY;
}
// case: creating new text not centered to parent elemenent → offset Y
// so that the text is centered to cursor position
if (!parentCenterPosition) {
mutateElement(element, {
y: element.y - element.baseline / 2,
});
}
}
@ -1539,9 +1526,7 @@ class App extends React.Component<any, AppState> {
});
this.handleTextWysiwyg(element, {
x: textX,
y: textY,
isExistingElement: isExistingTextElement,
isExistingElement: !!existingTextElement,
});
};
@ -1574,7 +1559,7 @@ class App extends React.Component<any, AppState> {
resetCursor();
const { x, y } = viewportCoordsToSceneCoords(
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
event,
this.state,
this.canvas,
@ -1588,8 +1573,8 @@ class App extends React.Component<any, AppState> {
const hitElement = getElementAtPosition(
elements,
this.state,
x,
y,
sceneX,
sceneY,
this.state.zoom,
);
@ -1616,11 +1601,9 @@ class App extends React.Component<any, AppState> {
resetCursor();
this.startTextEditing({
x: x,
y: y,
clientX: event.clientX,
clientY: event.clientY,
centerIfPossible: !event.altKey,
sceneX,
sceneY,
insertAtParentCenter: !event.altKey,
});
};
@ -2213,19 +2196,10 @@ class App extends React.Component<any, AppState> {
return;
}
const { x, y } = viewportCoordsToSceneCoords(
event,
this.state,
this.canvas,
window.devicePixelRatio,
);
this.startTextEditing({
x: x,
y: y,
clientX: event.clientX,
clientY: event.clientY,
centerIfPossible: !event.altKey,
sceneX: x,
sceneY: y,
insertAtParentCenter: !event.altKey,
});
resetCursor();
@ -2640,7 +2614,12 @@ class App extends React.Component<any, AppState> {
resizingElement: null,
selectionElement: null,
cursorButton: "up",
editingElement: multiElement ? this.state.editingElement : null,
// text elements are reset on finalize, and resetting on pointerup
// may cause issues with double taps
editingElement:
multiElement || isTextElement(this.state.editingElement)
? this.state.editingElement
: null,
});
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
@ -3006,7 +2985,9 @@ class App extends React.Component<any, AppState> {
scale: number,
) {
const elementClickedInside = getElementContainingPosition(
globalSceneState.getElementsIncludingDeleted(),
globalSceneState
.getElementsIncludingDeleted()
.filter((element) => !isTextElement(element)),
x,
y,
);
@ -3022,13 +3003,13 @@ class App extends React.Component<any, AppState> {
const isSnappedToCenter =
distanceToCenter < TEXT_TO_CENTER_SNAP_THRESHOLD;
if (isSnappedToCenter) {
const { x: wysiwygX, y: wysiwygY } = sceneCoordsToViewportCoords(
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
{ sceneX: elementCenterX, sceneY: elementCenterY },
state,
canvas,
scale,
);
return { wysiwygX, wysiwygY, elementCenterX, elementCenterY };
return { viewportX, viewportY, elementCenterX, elementCenterY };
}
}
}