You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

transform-single-session.ts 6.1KB

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