handlerRectangles.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { ExcalidrawElement, PointerType } from "./types";
  2. import { getElementAbsoluteCoords } from "./bounds";
  3. import { rotate } from "../math";
  4. type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se" | "rotation";
  5. const handleSizes: { [k in PointerType]: number } = {
  6. mouse: 8,
  7. pen: 16,
  8. touch: 28,
  9. };
  10. const ROTATION_HANDLER_GAP = 16;
  11. export const OMIT_SIDES_FOR_MULTIPLE_ELEMENTS = {
  12. e: true,
  13. s: true,
  14. n: true,
  15. w: true,
  16. rotation: true,
  17. };
  18. function generateHandler(
  19. x: number,
  20. y: number,
  21. width: number,
  22. height: number,
  23. cx: number,
  24. cy: number,
  25. angle: number,
  26. ): [number, number, number, number] {
  27. const [xx, yy] = rotate(x + width / 2, y + height / 2, cx, cy, angle);
  28. return [xx - width / 2, yy - height / 2, width, height];
  29. }
  30. export function handlerRectanglesFromCoords(
  31. [x1, y1, x2, y2]: [number, number, number, number],
  32. angle: number,
  33. zoom: number,
  34. pointerType: PointerType = "mouse",
  35. omitSides: { [T in Sides]?: boolean } = {},
  36. ): Partial<{ [T in Sides]: [number, number, number, number] }> {
  37. const size = handleSizes[pointerType];
  38. const handlerWidth = size / zoom;
  39. const handlerHeight = size / zoom;
  40. const handlerMarginX = size / zoom;
  41. const handlerMarginY = size / zoom;
  42. const width = x2 - x1;
  43. const height = y2 - y1;
  44. const cx = (x1 + x2) / 2;
  45. const cy = (y1 + y2) / 2;
  46. const dashedLineMargin = 4 / zoom;
  47. const centeringOffset = (size - 8) / (2 * zoom);
  48. const handlers: Partial<
  49. { [T in Sides]: [number, number, number, number] }
  50. > = {
  51. nw: omitSides["nw"]
  52. ? undefined
  53. : generateHandler(
  54. x1 - dashedLineMargin - handlerMarginX + centeringOffset,
  55. y1 - dashedLineMargin - handlerMarginY + centeringOffset,
  56. handlerWidth,
  57. handlerHeight,
  58. cx,
  59. cy,
  60. angle,
  61. ),
  62. ne: omitSides["ne"]
  63. ? undefined
  64. : generateHandler(
  65. x2 + dashedLineMargin - centeringOffset,
  66. y1 - dashedLineMargin - handlerMarginY + centeringOffset,
  67. handlerWidth,
  68. handlerHeight,
  69. cx,
  70. cy,
  71. angle,
  72. ),
  73. sw: omitSides["sw"]
  74. ? undefined
  75. : generateHandler(
  76. x1 - dashedLineMargin - handlerMarginX + centeringOffset,
  77. y2 + dashedLineMargin - centeringOffset,
  78. handlerWidth,
  79. handlerHeight,
  80. cx,
  81. cy,
  82. angle,
  83. ),
  84. se: omitSides["se"]
  85. ? undefined
  86. : generateHandler(
  87. x2 + dashedLineMargin - centeringOffset,
  88. y2 + dashedLineMargin - centeringOffset,
  89. handlerWidth,
  90. handlerHeight,
  91. cx,
  92. cy,
  93. angle,
  94. ),
  95. rotation: omitSides["rotation"]
  96. ? undefined
  97. : generateHandler(
  98. x1 + width / 2 - handlerWidth / 2,
  99. y1 -
  100. dashedLineMargin -
  101. handlerMarginY +
  102. centeringOffset -
  103. ROTATION_HANDLER_GAP / zoom,
  104. handlerWidth,
  105. handlerHeight,
  106. cx,
  107. cy,
  108. angle,
  109. ),
  110. };
  111. // We only want to show height handlers (all cardinal directions) above a certain size
  112. const minimumSizeForEightHandlers = (5 * size) / zoom;
  113. if (Math.abs(width) > minimumSizeForEightHandlers) {
  114. if (!omitSides["n"]) {
  115. handlers["n"] = generateHandler(
  116. x1 + width / 2 - handlerWidth / 2,
  117. y1 - dashedLineMargin - handlerMarginY + centeringOffset,
  118. handlerWidth,
  119. handlerHeight,
  120. cx,
  121. cy,
  122. angle,
  123. );
  124. }
  125. if (!omitSides["s"]) {
  126. handlers["s"] = generateHandler(
  127. x1 + width / 2 - handlerWidth / 2,
  128. y2 + dashedLineMargin - centeringOffset,
  129. handlerWidth,
  130. handlerHeight,
  131. cx,
  132. cy,
  133. angle,
  134. );
  135. }
  136. }
  137. if (Math.abs(height) > minimumSizeForEightHandlers) {
  138. if (!omitSides["w"]) {
  139. handlers["w"] = generateHandler(
  140. x1 - dashedLineMargin - handlerMarginX + centeringOffset,
  141. y1 + height / 2 - handlerHeight / 2,
  142. handlerWidth,
  143. handlerHeight,
  144. cx,
  145. cy,
  146. angle,
  147. );
  148. }
  149. if (!omitSides["e"]) {
  150. handlers["e"] = generateHandler(
  151. x2 + dashedLineMargin - centeringOffset,
  152. y1 + height / 2 - handlerHeight / 2,
  153. handlerWidth,
  154. handlerHeight,
  155. cx,
  156. cy,
  157. angle,
  158. );
  159. }
  160. }
  161. return handlers;
  162. }
  163. export function handlerRectangles(
  164. element: ExcalidrawElement,
  165. zoom: number,
  166. pointerType: PointerType = "mouse",
  167. ) {
  168. const handlers = handlerRectanglesFromCoords(
  169. getElementAbsoluteCoords(element),
  170. element.angle,
  171. zoom,
  172. pointerType,
  173. );
  174. if (element.type === "arrow" || element.type === "line") {
  175. if (element.points.length === 2) {
  176. // only check the last point because starting point is always (0,0)
  177. const [, p1] = element.points;
  178. if (p1[0] === 0 || p1[1] === 0) {
  179. return (
  180. {
  181. nw: handlers.nw,
  182. se: handlers.se,
  183. } as typeof handlers
  184. );
  185. }
  186. if (p1[0] > 0 && p1[1] < 0) {
  187. return (
  188. {
  189. ne: handlers.ne,
  190. sw: handlers.sw,
  191. } as typeof handlers
  192. );
  193. }
  194. if (p1[0] > 0 && p1[1] > 0) {
  195. return (
  196. {
  197. nw: handlers.nw,
  198. se: handlers.se,
  199. } as typeof handlers
  200. );
  201. }
  202. if (p1[0] < 0 && p1[1] > 0) {
  203. return (
  204. {
  205. ne: handlers.ne,
  206. sw: handlers.sw,
  207. } as typeof handlers
  208. );
  209. }
  210. if (p1[0] < 0 && p1[1] < 0) {
  211. return (
  212. {
  213. nw: handlers.nw,
  214. se: handlers.se,
  215. } as typeof handlers
  216. );
  217. }
  218. }
  219. }
  220. return handlers;
  221. }