Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

transforms.ts 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { Bounds, BoundsSnapshot, ShapeBounds } from "types"
  2. export function stretchshapesX(shapes: ShapeBounds[]) {
  3. const [first, ...rest] = shapes
  4. let min = first.minX
  5. let max = first.minX + first.width
  6. for (let box of rest) {
  7. min = Math.min(min, box.minX)
  8. max = Math.max(max, box.minX + box.width)
  9. }
  10. return shapes.map((box) => ({ ...box, x: min, width: max - min }))
  11. }
  12. export function stretchshapesY(shapes: ShapeBounds[]) {
  13. const [first, ...rest] = shapes
  14. let min = first.minY
  15. let max = first.minY + first.height
  16. for (let box of rest) {
  17. min = Math.min(min, box.minY)
  18. max = Math.max(max, box.minY + box.height)
  19. }
  20. return shapes.map((box) => ({ ...box, y: min, height: max - min }))
  21. }
  22. export function distributeshapesX(shapes: ShapeBounds[]) {
  23. const len = shapes.length
  24. const sorted = [...shapes].sort((a, b) => a.minX - b.minX)
  25. let min = sorted[0].minX
  26. sorted.sort((a, b) => a.minX + a.width - b.minX - b.width)
  27. let last = sorted[len - 1]
  28. let max = last.minX + last.width
  29. let range = max - min
  30. let step = range / len
  31. return sorted.map((box, i) => ({ ...box, x: min + step * i }))
  32. }
  33. export function distributeshapesY(shapes: ShapeBounds[]) {
  34. const len = shapes.length
  35. const sorted = [...shapes].sort((a, b) => a.minY - b.minY)
  36. let min = sorted[0].minY
  37. sorted.sort((a, b) => a.minY + a.height - b.minY - b.height)
  38. let last = sorted[len - 1]
  39. let max = last.minY + last.height
  40. let range = max - min
  41. let step = range / len
  42. return sorted.map((box, i) => ({ ...box, y: min + step * i }))
  43. }
  44. export function alignshapesCenterX(shapes: ShapeBounds[]) {
  45. let midX = 0
  46. for (let box of shapes) midX += box.minX + box.width / 2
  47. midX /= shapes.length
  48. return shapes.map((box) => ({ ...box, x: midX - box.width / 2 }))
  49. }
  50. export function alignshapesCenterY(shapes: ShapeBounds[]) {
  51. let midY = 0
  52. for (let box of shapes) midY += box.minY + box.height / 2
  53. midY /= shapes.length
  54. return shapes.map((box) => ({ ...box, y: midY - box.height / 2 }))
  55. }
  56. export function alignshapesTop(shapes: ShapeBounds[]) {
  57. const [first, ...rest] = shapes
  58. let y = first.minY
  59. for (let box of rest) if (box.minY < y) y = box.minY
  60. return shapes.map((box) => ({ ...box, y }))
  61. }
  62. export function alignshapesBottom(shapes: ShapeBounds[]) {
  63. const [first, ...rest] = shapes
  64. let maxY = first.minY + first.height
  65. for (let box of rest)
  66. if (box.minY + box.height > maxY) maxY = box.minY + box.height
  67. return shapes.map((box) => ({ ...box, y: maxY - box.height }))
  68. }
  69. export function alignshapesLeft(shapes: ShapeBounds[]) {
  70. const [first, ...rest] = shapes
  71. let x = first.minX
  72. for (let box of rest) if (box.minX < x) x = box.minX
  73. return shapes.map((box) => ({ ...box, x }))
  74. }
  75. export function alignshapesRight(shapes: ShapeBounds[]) {
  76. const [first, ...rest] = shapes
  77. let maxX = first.minX + first.width
  78. for (let box of rest)
  79. if (box.minX + box.width > maxX) maxX = box.minX + box.width
  80. return shapes.map((box) => ({ ...box, x: maxX - box.width }))
  81. }
  82. // Resizers
  83. export function getBoundingBox(shapes: ShapeBounds[]): Bounds {
  84. if (shapes.length === 0) {
  85. return {
  86. minX: 0,
  87. minY: 0,
  88. maxX: 0,
  89. maxY: 0,
  90. width: 0,
  91. height: 0,
  92. }
  93. }
  94. const first = shapes[0]
  95. let minX = first.minX
  96. let minY = first.minY
  97. let maxX = first.minX + first.width
  98. let maxY = first.minY + first.height
  99. for (let box of shapes) {
  100. minX = Math.min(minX, box.minX)
  101. minY = Math.min(minY, box.minY)
  102. maxX = Math.max(maxX, box.minX + box.width)
  103. maxY = Math.max(maxY, box.minY + box.height)
  104. }
  105. return {
  106. minX,
  107. minY,
  108. maxX,
  109. maxY,
  110. width: maxX - minX,
  111. height: maxY - minY,
  112. }
  113. }
  114. export function getSnapshots(
  115. shapes: ShapeBounds[],
  116. bounds: Bounds
  117. ): Record<string, BoundsSnapshot> {
  118. const acc = {} as Record<string, BoundsSnapshot>
  119. const w = bounds.maxX - bounds.minX
  120. const h = bounds.maxY - bounds.minY
  121. for (let box of shapes) {
  122. acc[box.id] = {
  123. ...box,
  124. nx: (box.minX - bounds.minX) / w,
  125. ny: (box.minY - bounds.minY) / h,
  126. nmx: 1 - (box.minX + box.width - bounds.minX) / w,
  127. nmy: 1 - (box.minY + box.height - bounds.minY) / h,
  128. nw: box.width / w,
  129. nh: box.height / h,
  130. }
  131. }
  132. return acc
  133. }
  134. export function getEdgeResizer(shapes: ShapeBounds[], edge: number) {
  135. const initial = getBoundingBox(shapes)
  136. const snapshots = getSnapshots(shapes, initial)
  137. const mshapes = [...shapes]
  138. let { minX: x0, minY: y0, maxX: x1, maxY: y1 } = initial
  139. let { minX: mx, minY: my } = initial
  140. let mw = x1 - x0
  141. let mh = y1 - y0
  142. return function edgeResize({ x, y }) {
  143. if (edge === 0 || edge === 2) {
  144. edge === 0 ? (y0 = y) : (y1 = y)
  145. my = y0 < y1 ? y0 : y1
  146. mh = Math.abs(y1 - y0)
  147. for (let box of mshapes) {
  148. const { ny, nmy, nh } = snapshots[box.id]
  149. box.minY = my + (y1 < y0 ? nmy : ny) * mh
  150. box.height = nh * mh
  151. }
  152. } else {
  153. edge === 1 ? (x1 = x) : (x0 = x)
  154. mx = x0 < x1 ? x0 : x1
  155. mw = Math.abs(x1 - x0)
  156. for (let box of mshapes) {
  157. const { nx, nmx, nw } = snapshots[box.id]
  158. box.minX = mx + (x1 < x0 ? nmx : nx) * mw
  159. box.width = nw * mw
  160. }
  161. }
  162. return [
  163. mshapes,
  164. {
  165. x: mx,
  166. y: my,
  167. width: mw,
  168. height: mh,
  169. maxX: mx + mw,
  170. maxY: my + mh,
  171. },
  172. ]
  173. }
  174. }
  175. /**
  176. * Returns a function that can be used to calculate corner resize transforms.
  177. * @param shapes An array of the shapes being resized.
  178. * @param corner A number representing the corner being dragged. Top Left: 0, Top Right: 1, Bottom Right: 2, Bottom Left: 3.
  179. * @example
  180. * const resizer = getCornerResizer(selectedshapes, 3)
  181. * resizer(selectedshapes, )
  182. */
  183. export function getCornerResizer(shapes: ShapeBounds[], corner: number) {
  184. const initial = getBoundingBox(shapes)
  185. const snapshots = getSnapshots(shapes, initial)
  186. const mshapes = [...shapes]
  187. let { minX: x0, minY: y0, maxX: x1, maxY: y1 } = initial
  188. let { minX: mx, minY: my } = initial
  189. let mw = x1 - x0
  190. let mh = y1 - y0
  191. return function cornerResizer({ x, y }) {
  192. corner < 2 ? (y0 = y) : (y1 = y)
  193. my = y0 < y1 ? y0 : y1
  194. mh = Math.abs(y1 - y0)
  195. corner === 1 || corner === 2 ? (x1 = x) : (x0 = x)
  196. mx = x0 < x1 ? x0 : x1
  197. mw = Math.abs(x1 - x0)
  198. for (let box of mshapes) {
  199. const { nx, nmx, nw, ny, nmy, nh } = snapshots[box.id]
  200. box.minX = mx + (x1 < x0 ? nmx : nx) * mw
  201. box.minY = my + (y1 < y0 ? nmy : ny) * mh
  202. box.width = nw * mw
  203. box.height = nh * mh
  204. }
  205. return [
  206. mshapes,
  207. {
  208. x: mx,
  209. y: my,
  210. width: mw,
  211. height: mh,
  212. maxX: mx + mw,
  213. maxY: my + mh,
  214. },
  215. ]
  216. }
  217. }