Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

transform-session.ts 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. import {
  2. Data,
  3. TransformEdge,
  4. TransformCorner,
  5. Bounds,
  6. BoundsSnapshot,
  7. } from "types"
  8. import * as vec from "utils/vec"
  9. import BaseSession from "./base-session"
  10. import commands from "state/commands"
  11. import { current } from "immer"
  12. import { getShapeUtils } from "lib/shapes"
  13. import { getCommonBounds, getTransformAnchor } from "utils/utils"
  14. export default class TransformSession extends BaseSession {
  15. delta = [0, 0]
  16. isFlippedX = false
  17. isFlippedY = false
  18. transformType: TransformEdge | TransformCorner
  19. origin: number[]
  20. snapshot: TransformSnapshot
  21. corners: {
  22. a: number[]
  23. b: number[]
  24. }
  25. constructor(
  26. data: Data,
  27. transformType: TransformCorner | TransformEdge,
  28. point: number[]
  29. ) {
  30. super(data)
  31. this.origin = point
  32. this.transformType = transformType
  33. // if (data.selectedIds.size === 1) {
  34. // const shape =
  35. // data.document.pages[data.currentPageId].shapes[
  36. // Array.from(data.selectedIds.values())[0]
  37. // ]
  38. // if (shape.rotation > 0) {
  39. // }
  40. // }
  41. this.snapshot = getTransformSnapshot(data, transformType)
  42. const { minX, minY, maxX, maxY } = this.snapshot.initialBounds
  43. this.corners = {
  44. a: [minX, minY],
  45. b: [maxX, maxY],
  46. }
  47. }
  48. update(data: Data, point: number[]) {
  49. const {
  50. corners: { a, b },
  51. transformType,
  52. } = this
  53. const {
  54. boundsRotation,
  55. shapeBounds,
  56. initialBounds,
  57. currentPageId,
  58. selectedIds,
  59. } = this.snapshot
  60. const { shapes } = data.document.pages[currentPageId]
  61. let delta = vec.vec(this.origin, point)
  62. // if (isSingle) {
  63. // const center = [
  64. // initialBounds.minX + initialBounds.width / 2,
  65. // initialBounds.minY + initialBounds.height / 2,
  66. // ]
  67. // const rotation = shapes[Array.from(selectedIds.values())[0]].rotation
  68. // const rotatedOrigin = vec.rotWith(this.origin, center, -rotation)
  69. // const rotatedPoint = vec.rotWith(point, center, -rotation)
  70. // delta = vec.vec(rotatedOrigin, rotatedPoint)
  71. // }
  72. /*
  73. Transforms
  74. Corners a and b are the original top-left and bottom-right corners of the
  75. bounding box. Depending on what the user is dragging, change one or both
  76. points. To keep things smooth, calculate based by adding the delta (the
  77. vector between the current point and its original point) to the original
  78. bounding box values.
  79. */
  80. switch (transformType) {
  81. case TransformEdge.Top: {
  82. a[1] = initialBounds.minY + delta[1]
  83. break
  84. }
  85. case TransformEdge.Right: {
  86. b[0] = initialBounds.maxX + delta[0]
  87. break
  88. }
  89. case TransformEdge.Bottom: {
  90. b[1] = initialBounds.maxY + delta[1]
  91. break
  92. }
  93. case TransformEdge.Left: {
  94. a[0] = initialBounds.minX + delta[0]
  95. break
  96. }
  97. case TransformCorner.TopLeft: {
  98. a[0] = initialBounds.minX + delta[0]
  99. a[1] = initialBounds.minY + delta[1]
  100. break
  101. }
  102. case TransformCorner.TopRight: {
  103. a[1] = initialBounds.minY + delta[1]
  104. b[0] = initialBounds.maxX + delta[0]
  105. break
  106. }
  107. case TransformCorner.BottomRight: {
  108. b[0] = initialBounds.maxX + delta[0]
  109. b[1] = initialBounds.maxY + delta[1]
  110. break
  111. }
  112. case TransformCorner.BottomLeft: {
  113. a[0] = initialBounds.minX + delta[0]
  114. b[1] = initialBounds.maxY + delta[1]
  115. break
  116. }
  117. }
  118. // Calculate new common (externior) bounding box
  119. const newBounds = {
  120. minX: Math.min(a[0], b[0]),
  121. minY: Math.min(a[1], b[1]),
  122. maxX: Math.max(a[0], b[0]),
  123. maxY: Math.max(a[1], b[1]),
  124. width: Math.abs(b[0] - a[0]),
  125. height: Math.abs(b[1] - a[1]),
  126. }
  127. this.isFlippedX = b[0] < a[0]
  128. this.isFlippedY = b[1] < a[1]
  129. // Now work backward to calculate a new bounding box for each of the shapes.
  130. selectedIds.forEach((id) => {
  131. const { initialShape, initialShapeBounds } = shapeBounds[id]
  132. const { nx, nmx, nw, ny, nmy, nh } = initialShapeBounds
  133. const shape = shapes[id]
  134. const minX =
  135. newBounds.minX + (this.isFlippedX ? nmx : nx) * newBounds.width
  136. const minY =
  137. newBounds.minY + (this.isFlippedY ? nmy : ny) * newBounds.height
  138. const width = nw * newBounds.width
  139. const height = nh * newBounds.height
  140. const newShapeBounds = {
  141. minX,
  142. minY,
  143. maxX: minX + width,
  144. maxY: minY + height,
  145. width,
  146. height,
  147. isFlippedX: this.isFlippedX,
  148. isFlippedY: this.isFlippedY,
  149. }
  150. // Pass the new data to the shape's transform utility for mutation.
  151. // Most shapes should be able to transform using only the bounding box,
  152. // however some shapes (e.g. those with internal points) will need more
  153. // data here too.
  154. getShapeUtils(shape).transform(shape, newShapeBounds, {
  155. type: this.transformType,
  156. initialShape,
  157. initialShapeBounds,
  158. initialBounds,
  159. boundsRotation,
  160. isFlippedX: this.isFlippedX,
  161. isFlippedY: this.isFlippedY,
  162. isSingle: false,
  163. anchor: getTransformAnchor(
  164. this.transformType,
  165. this.isFlippedX,
  166. this.isFlippedY
  167. ),
  168. })
  169. })
  170. }
  171. cancel(data: Data) {
  172. const {
  173. shapeBounds,
  174. boundsRotation,
  175. initialBounds,
  176. currentPageId,
  177. selectedIds,
  178. } = this.snapshot
  179. const { shapes } = data.document.pages[currentPageId]
  180. selectedIds.forEach((id) => {
  181. const shape = shapes[id]
  182. const { initialShape, initialShapeBounds } = shapeBounds[id]
  183. getShapeUtils(shape).transform(shape, initialShapeBounds, {
  184. type: this.transformType,
  185. initialShape,
  186. initialShapeBounds,
  187. initialBounds,
  188. boundsRotation,
  189. isFlippedX: false,
  190. isFlippedY: false,
  191. isSingle: false,
  192. anchor: getTransformAnchor(this.transformType, false, false),
  193. })
  194. })
  195. }
  196. complete(data: Data) {
  197. commands.transform(
  198. data,
  199. this.snapshot,
  200. getTransformSnapshot(data, this.transformType),
  201. getTransformAnchor(this.transformType, false, false)
  202. )
  203. }
  204. }
  205. export function getTransformSnapshot(
  206. data: Data,
  207. transformType: TransformEdge | TransformCorner
  208. ) {
  209. const {
  210. document: { pages },
  211. selectedIds,
  212. currentPageId,
  213. boundsRotation,
  214. } = current(data)
  215. const pageShapes = pages[currentPageId].shapes
  216. // A mapping of selected shapes and their bounds
  217. const shapesBounds = Object.fromEntries(
  218. Array.from(selectedIds.values()).map((id) => {
  219. const shape = pageShapes[id]
  220. return [shape.id, getShapeUtils(shape).getBounds(shape)]
  221. })
  222. )
  223. // The common (exterior) bounds of the selected shapes
  224. const bounds = getCommonBounds(...Object.values(shapesBounds))
  225. // Return a mapping of shapes to bounds together with the relative
  226. // positions of the shape's bounds within the common bounds shape.
  227. return {
  228. currentPageId,
  229. type: transformType,
  230. initialBounds: bounds,
  231. boundsRotation,
  232. selectedIds: new Set(selectedIds),
  233. shapeBounds: Object.fromEntries(
  234. Array.from(selectedIds.values()).map((id) => {
  235. const { minX, minY, width, height } = shapesBounds[id]
  236. return [
  237. id,
  238. {
  239. initialShape: pageShapes[id],
  240. initialShapeBounds: {
  241. ...shapesBounds[id],
  242. nx: (minX - bounds.minX) / bounds.width,
  243. ny: (minY - bounds.minY) / bounds.height,
  244. nmx: 1 - (minX + width - bounds.minX) / bounds.width,
  245. nmy: 1 - (minY + height - bounds.minY) / bounds.height,
  246. nw: width / bounds.width,
  247. nh: height / bounds.height,
  248. },
  249. },
  250. ]
  251. })
  252. ),
  253. }
  254. }
  255. export type TransformSnapshot = ReturnType<typeof getTransformSnapshot>