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-session.ts 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import { Data, TransformEdge, TransformCorner, Bounds } 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 { getCommonBounds } from "utils/utils"
  8. export default class TransformSession extends BaseSession {
  9. delta = [0, 0]
  10. transformType: TransformEdge | TransformCorner
  11. origin: number[]
  12. snapshot: TransformSnapshot
  13. currentBounds: Bounds
  14. corners: {
  15. a: number[]
  16. b: number[]
  17. }
  18. constructor(
  19. data: Data,
  20. type: TransformCorner | TransformEdge,
  21. point: number[]
  22. ) {
  23. super(data)
  24. this.origin = point
  25. this.transformType = type
  26. this.snapshot = getTransformSnapshot(data)
  27. const { minX, minY, maxX, maxY } = this.snapshot.initialBounds
  28. this.currentBounds = { ...this.snapshot.initialBounds }
  29. this.corners = {
  30. a: [minX, minY],
  31. b: [maxX, maxY],
  32. }
  33. }
  34. update(data: Data, point: number[]) {
  35. const { shapeBounds, currentPageId, selectedIds } = this.snapshot
  36. const {
  37. document: { pages },
  38. } = data
  39. let [x, y] = point
  40. const { corners, transformType } = this
  41. // Edge Transform
  42. switch (transformType) {
  43. case TransformEdge.Top: {
  44. corners.a[1] = y
  45. break
  46. }
  47. case TransformEdge.Right: {
  48. corners.b[0] = x
  49. break
  50. }
  51. case TransformEdge.Bottom: {
  52. corners.b[1] = y
  53. break
  54. }
  55. case TransformEdge.Left: {
  56. corners.a[0] = x
  57. break
  58. }
  59. case TransformCorner.TopLeft: {
  60. corners.a[1] = y
  61. corners.a[0] = x
  62. break
  63. }
  64. case TransformCorner.TopRight: {
  65. corners.b[0] = x
  66. corners.a[1] = y
  67. break
  68. }
  69. case TransformCorner.BottomRight: {
  70. corners.b[1] = y
  71. corners.b[0] = x
  72. break
  73. }
  74. case TransformCorner.BottomLeft: {
  75. corners.a[0] = x
  76. corners.b[1] = y
  77. break
  78. }
  79. }
  80. const newBounds = {
  81. minX: Math.min(corners.a[0], corners.b[0]),
  82. minY: Math.min(corners.a[1], corners.b[1]),
  83. maxX: Math.max(corners.a[0], corners.b[0]),
  84. maxY: Math.max(corners.a[1], corners.b[1]),
  85. width: Math.abs(corners.b[0] - corners.a[0]),
  86. height: Math.abs(corners.b[1] - corners.a[1]),
  87. }
  88. const isFlippedX = corners.b[0] - corners.a[0] < 0
  89. const isFlippedY = corners.b[1] - corners.a[1] < 0
  90. // const dx = newBounds.minX - currentBounds.minX
  91. // const dy = newBounds.minY - currentBounds.minY
  92. // const scaleX = newBounds.width / currentBounds.width
  93. // const scaleY = newBounds.height / currentBounds.height
  94. this.currentBounds = newBounds
  95. selectedIds.forEach((id) => {
  96. const { nx, nmx, nw, ny, nmy, nh } = shapeBounds[id]
  97. const minX = newBounds.minX + (isFlippedX ? nmx : nx) * newBounds.width
  98. const minY = newBounds.minY + (isFlippedY ? nmy : ny) * newBounds.height
  99. const width = nw * newBounds.width
  100. const height = nh * newBounds.height
  101. const shape = pages[currentPageId].shapes[id]
  102. getShapeUtils(shape).transform(shape, {
  103. minX,
  104. minY,
  105. maxX: minX + width,
  106. maxY: minY + height,
  107. width,
  108. height,
  109. })
  110. // utils.stretch(shape, scaleX, scaleY)
  111. })
  112. // switch (this.transformHandle) {
  113. // case TransformEdge.Top:
  114. // case TransformEdge.Left:
  115. // case TransformEdge.Right:
  116. // case TransformEdge.Bottom: {
  117. // for (let id in shapeBounds) {
  118. // const { ny, nmy, nh } = shapeBounds[id]
  119. // const minY = v.my + (v.y1 < v.y0 ? nmy : ny) * v.mh
  120. // const height = nh * v.mh
  121. // const shape = pages[currentPageId].shapes[id]
  122. // getShapeUtils(shape).transform(shape)
  123. // }
  124. // }
  125. // case TransformCorner.TopLeft:
  126. // case TransformCorner.TopRight:
  127. // case TransformCorner.BottomLeft:
  128. // case TransformCorner.BottomRight: {
  129. // }
  130. // }
  131. }
  132. cancel(data: Data) {
  133. const { currentPageId } = this.snapshot
  134. const { document } = data
  135. // for (let id in shapes) {
  136. // Restore shape using original bounds
  137. // document.pages[currentPageId].shapes[id]
  138. // }
  139. }
  140. complete(data: Data) {
  141. // commands.translate(data, this.snapshot, getTransformSnapshot(data))
  142. }
  143. }
  144. export function getTransformSnapshot(data: Data) {
  145. const {
  146. document: { pages },
  147. selectedIds,
  148. currentPageId,
  149. } = current(data)
  150. // A mapping of selected shapes and their bounds
  151. const shapesBounds = Object.fromEntries(
  152. Array.from(selectedIds.values()).map((id) => {
  153. const shape = pages[currentPageId].shapes[id]
  154. return [shape.id, getShapeUtils(shape).getBounds(shape)]
  155. })
  156. )
  157. // The common (exterior) bounds of the selected shapes
  158. const bounds = getCommonBounds(
  159. ...Array.from(selectedIds.values()).map((id) => {
  160. const shape = pages[currentPageId].shapes[id]
  161. return getShapeUtils(shape).getBounds(shape)
  162. })
  163. )
  164. // Return a mapping of shapes to bounds together with the relative
  165. // positions of the shape's bounds within the common bounds shape.
  166. return {
  167. currentPageId,
  168. initialBounds: bounds,
  169. selectedIds: new Set(selectedIds),
  170. shapeBounds: Object.fromEntries(
  171. Array.from(selectedIds.values()).map((id) => {
  172. const { minX, minY, width, height } = shapesBounds[id]
  173. return [
  174. id,
  175. {
  176. ...bounds,
  177. nx: (minX - bounds.minX) / bounds.width,
  178. ny: (minY - bounds.minY) / bounds.height,
  179. nmx: 1 - (minX + width - bounds.minX) / bounds.width,
  180. nmy: 1 - (minY + height - bounds.minY) / bounds.height,
  181. nw: width / bounds.width,
  182. nh: height / bounds.height,
  183. },
  184. ]
  185. })
  186. ),
  187. }
  188. }
  189. export type TransformSnapshot = ReturnType<typeof getTransformSnapshot>