選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

transform-session.ts 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { Data, Edge, Corner } 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/shape-utils'
  7. import {
  8. getBoundsCenter,
  9. getBoundsFromPoints,
  10. getCommonBounds,
  11. getPage,
  12. getRelativeTransformedBoundingBox,
  13. getSelectedShapes,
  14. getShapes,
  15. getTransformedBoundingBox,
  16. } from 'utils/utils'
  17. export default class TransformSession extends BaseSession {
  18. scaleX = 1
  19. scaleY = 1
  20. transformType: Edge | Corner
  21. origin: number[]
  22. snapshot: TransformSnapshot
  23. constructor(data: Data, transformType: Corner | Edge, point: number[]) {
  24. super(data)
  25. this.origin = point
  26. this.transformType = transformType
  27. this.snapshot = getTransformSnapshot(data, transformType)
  28. }
  29. update(data: Data, point: number[], isAspectRatioLocked = false) {
  30. const { transformType } = this
  31. const { shapeBounds, initialBounds, isAllAspectRatioLocked } = this.snapshot
  32. const { shapes } = getPage(data)
  33. const newBoundingBox = getTransformedBoundingBox(
  34. initialBounds,
  35. transformType,
  36. vec.vec(this.origin, point),
  37. data.boundsRotation,
  38. isAspectRatioLocked || isAllAspectRatioLocked
  39. )
  40. this.scaleX = newBoundingBox.scaleX
  41. this.scaleY = newBoundingBox.scaleY
  42. // Now work backward to calculate a new bounding box for each of the shapes.
  43. for (let id in shapeBounds) {
  44. const { initialShape, initialShapeBounds, transformOrigin } =
  45. shapeBounds[id]
  46. const newShapeBounds = getRelativeTransformedBoundingBox(
  47. newBoundingBox,
  48. initialBounds,
  49. initialShapeBounds,
  50. this.scaleX < 0,
  51. this.scaleY < 0
  52. )
  53. const shape = shapes[id]
  54. getShapeUtils(shape).transform(shape, newShapeBounds, {
  55. type: this.transformType,
  56. initialShape,
  57. scaleX: this.scaleX,
  58. scaleY: this.scaleY,
  59. transformOrigin,
  60. })
  61. }
  62. }
  63. cancel(data: Data) {
  64. const { currentPageId, shapeBounds } = this.snapshot
  65. const page = getPage(data, currentPageId)
  66. for (let id in shapeBounds) {
  67. const shape = page.shapes[id]
  68. const { initialShape, initialShapeBounds, transformOrigin } =
  69. shapeBounds[id]
  70. getShapeUtils(shape).transform(shape, initialShapeBounds, {
  71. type: this.transformType,
  72. initialShape,
  73. scaleX: 1,
  74. scaleY: 1,
  75. transformOrigin,
  76. })
  77. }
  78. }
  79. complete(data: Data) {
  80. if (!this.snapshot.hasUnlockedShapes) return
  81. commands.transform(
  82. data,
  83. this.snapshot,
  84. getTransformSnapshot(data, this.transformType)
  85. )
  86. }
  87. }
  88. export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
  89. const cData = current(data)
  90. const { currentPageId } = cData
  91. const initialShapes = getSelectedShapes(cData).filter(
  92. (shape) => !shape.isLocked
  93. )
  94. const hasUnlockedShapes = initialShapes.length > 0
  95. const isAllAspectRatioLocked = initialShapes.every(
  96. (shape) => shape.isAspectRatioLocked
  97. )
  98. const shapesBounds = Object.fromEntries(
  99. initialShapes.map((shape) => [
  100. shape.id,
  101. getShapeUtils(shape).getBounds(shape),
  102. ])
  103. )
  104. const boundsArr = Object.values(shapesBounds)
  105. const commonBounds = getCommonBounds(...boundsArr)
  106. const initialInnerBounds = getBoundsFromPoints(boundsArr.map(getBoundsCenter))
  107. // Return a mapping of shapes to bounds together with the relative
  108. // positions of the shape's bounds within the common bounds shape.
  109. return {
  110. type: transformType,
  111. hasUnlockedShapes,
  112. isAllAspectRatioLocked,
  113. currentPageId,
  114. initialBounds: commonBounds,
  115. shapeBounds: Object.fromEntries(
  116. initialShapes.map((shape) => {
  117. const initialShapeBounds = shapesBounds[shape.id]
  118. const ic = getBoundsCenter(initialShapeBounds)
  119. let ix = (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width
  120. let iy = (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height
  121. return [
  122. shape.id,
  123. {
  124. initialShape: shape,
  125. initialShapeBounds,
  126. transformOrigin: [ix, iy],
  127. },
  128. ]
  129. })
  130. ),
  131. }
  132. }
  133. export type TransformSnapshot = ReturnType<typeof getTransformSnapshot>
  134. // const transformOrigins = {
  135. // [Edge.Top]: [0.5, 1],
  136. // [Edge.Right]: [0, 0.5],
  137. // [Edge.Bottom]: [0.5, 0],
  138. // [Edge.Left]: [1, 0.5],
  139. // [Corner.TopLeft]: [1, 1],
  140. // [Corner.TopRight]: [0, 1],
  141. // [Corner.BottomLeft]: [1, 0],
  142. // [Corner.BottomRight]: [0, 0],
  143. // }
  144. // const origin = transformOrigins[this.transformType]