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

transform-session.ts 5.4KB

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