dragElements.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { updateBoundElements } from "./binding";
  2. import { getCommonBounds } from "./bounds";
  3. import { mutateElement } from "./mutateElement";
  4. import { getPerfectElementSize } from "./sizeHelpers";
  5. import { NonDeletedExcalidrawElement } from "./types";
  6. import { AppState, PointerDownState } from "../types";
  7. import { getBoundTextElement } from "./textElement";
  8. import { isSelectedViaGroup } from "../groups";
  9. export const dragSelectedElements = (
  10. pointerDownState: PointerDownState,
  11. selectedElements: NonDeletedExcalidrawElement[],
  12. pointerX: number,
  13. pointerY: number,
  14. lockDirection: boolean = false,
  15. distanceX: number = 0,
  16. distanceY: number = 0,
  17. appState: AppState,
  18. ) => {
  19. const [x1, y1] = getCommonBounds(selectedElements);
  20. const offset = { x: pointerX - x1, y: pointerY - y1 };
  21. selectedElements.forEach((element) => {
  22. updateElementCoords(
  23. lockDirection,
  24. distanceX,
  25. distanceY,
  26. pointerDownState,
  27. element,
  28. offset,
  29. );
  30. // update coords of bound text only if we're dragging the container directly
  31. // (we don't drag the group that it's part of)
  32. if (
  33. // container isn't part of any group
  34. // (perf optim so we don't check `isSelectedViaGroup()` in every case)
  35. !element.groupIds.length ||
  36. // container is part of a group, but we're dragging the container directly
  37. (appState.editingGroupId && !isSelectedViaGroup(appState, element))
  38. ) {
  39. const textElement = getBoundTextElement(element);
  40. if (textElement) {
  41. updateElementCoords(
  42. lockDirection,
  43. distanceX,
  44. distanceY,
  45. pointerDownState,
  46. textElement,
  47. offset,
  48. );
  49. }
  50. }
  51. updateBoundElements(element, {
  52. simultaneouslyUpdated: selectedElements,
  53. });
  54. });
  55. };
  56. const updateElementCoords = (
  57. lockDirection: boolean,
  58. distanceX: number,
  59. distanceY: number,
  60. pointerDownState: PointerDownState,
  61. element: NonDeletedExcalidrawElement,
  62. offset: { x: number; y: number },
  63. ) => {
  64. let x: number;
  65. let y: number;
  66. if (lockDirection) {
  67. const lockX = lockDirection && distanceX < distanceY;
  68. const lockY = lockDirection && distanceX > distanceY;
  69. const original = pointerDownState.originalElements.get(element.id);
  70. x = lockX && original ? original.x : element.x + offset.x;
  71. y = lockY && original ? original.y : element.y + offset.y;
  72. } else {
  73. x = element.x + offset.x;
  74. y = element.y + offset.y;
  75. }
  76. mutateElement(element, {
  77. x,
  78. y,
  79. });
  80. };
  81. export const getDragOffsetXY = (
  82. selectedElements: NonDeletedExcalidrawElement[],
  83. x: number,
  84. y: number,
  85. ): [number, number] => {
  86. const [x1, y1] = getCommonBounds(selectedElements);
  87. return [x - x1, y - y1];
  88. };
  89. export const dragNewElement = (
  90. draggingElement: NonDeletedExcalidrawElement,
  91. elementType: AppState["activeTool"]["type"],
  92. originX: number,
  93. originY: number,
  94. x: number,
  95. y: number,
  96. width: number,
  97. height: number,
  98. shouldMaintainAspectRatio: boolean,
  99. shouldResizeFromCenter: boolean,
  100. /** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
  101. true */
  102. widthAspectRatio?: number | null,
  103. ) => {
  104. if (shouldMaintainAspectRatio && draggingElement.type !== "selection") {
  105. if (widthAspectRatio) {
  106. height = width / widthAspectRatio;
  107. } else {
  108. // Depending on where the cursor is at (x, y) relative to where the starting point is
  109. // (originX, originY), we use ONLY width or height to control size increase.
  110. // This allows the cursor to always "stick" to one of the sides of the bounding box.
  111. if (Math.abs(y - originY) > Math.abs(x - originX)) {
  112. ({ width, height } = getPerfectElementSize(
  113. elementType,
  114. height,
  115. x < originX ? -width : width,
  116. ));
  117. } else {
  118. ({ width, height } = getPerfectElementSize(
  119. elementType,
  120. width,
  121. y < originY ? -height : height,
  122. ));
  123. }
  124. if (height < 0) {
  125. height = -height;
  126. }
  127. }
  128. }
  129. let newX = x < originX ? originX - width : originX;
  130. let newY = y < originY ? originY - height : originY;
  131. if (shouldResizeFromCenter) {
  132. width += width;
  133. height += height;
  134. newX = originX - width / 2;
  135. newY = originY - height / 2;
  136. }
  137. if (width !== 0 && height !== 0) {
  138. mutateElement(draggingElement, {
  139. x: newX,
  140. y: newY,
  141. width,
  142. height,
  143. });
  144. }
  145. };