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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import state, { useSelector } from "state"
  2. import styled from "styles"
  3. import inputs from "state/inputs"
  4. import { useRef } from "react"
  5. import { TransformCorner, TransformEdge } from "types"
  6. import { lerp } from "utils/utils"
  7. export default function Bounds() {
  8. const isBrushing = useSelector((s) => s.isIn("brushSelecting"))
  9. const zoom = useSelector((s) => s.data.camera.zoom)
  10. const bounds = useSelector((s) => s.values.selectedBounds)
  11. const rotation = useSelector((s) => {
  12. if (s.data.selectedIds.size === 1) {
  13. const { shapes } = s.data.document.pages[s.data.currentPageId]
  14. const selected = Array.from(s.data.selectedIds.values())[0]
  15. return shapes[selected].rotation
  16. } else {
  17. return 0
  18. }
  19. })
  20. if (!bounds) return null
  21. let { minX, minY, maxX, maxY, width, height } = bounds
  22. const p = 4 / zoom
  23. const cp = p * 2
  24. return (
  25. <g
  26. pointerEvents={isBrushing ? "none" : "all"}
  27. transform={`rotate(${rotation * (180 / Math.PI)},${minX + width / 2}, ${
  28. minY + height / 2
  29. })`}
  30. >
  31. <StyledBounds
  32. x={minX}
  33. y={minY}
  34. width={width}
  35. height={height}
  36. pointerEvents="none"
  37. />
  38. <EdgeHorizontal
  39. x={minX + p}
  40. y={minY}
  41. width={Math.max(0, width - p * 2)}
  42. height={p}
  43. edge={TransformEdge.Top}
  44. />
  45. <EdgeVertical
  46. x={maxX}
  47. y={minY + p}
  48. width={p}
  49. height={Math.max(0, height - p * 2)}
  50. edge={TransformEdge.Right}
  51. />
  52. <EdgeHorizontal
  53. x={minX + p}
  54. y={maxY}
  55. width={Math.max(0, width - p * 2)}
  56. height={p}
  57. edge={TransformEdge.Bottom}
  58. />
  59. <EdgeVertical
  60. x={minX}
  61. y={minY + p}
  62. width={p}
  63. height={Math.max(0, height - p * 2)}
  64. edge={TransformEdge.Left}
  65. />
  66. <Corner
  67. x={minX}
  68. y={minY}
  69. width={cp}
  70. height={cp}
  71. corner={TransformCorner.TopLeft}
  72. />
  73. <Corner
  74. x={maxX}
  75. y={minY}
  76. width={cp}
  77. height={cp}
  78. corner={TransformCorner.TopRight}
  79. />
  80. <Corner
  81. x={maxX}
  82. y={maxY}
  83. width={cp}
  84. height={cp}
  85. corner={TransformCorner.BottomRight}
  86. />
  87. <Corner
  88. x={minX}
  89. y={maxY}
  90. width={cp}
  91. height={cp}
  92. corner={TransformCorner.BottomLeft}
  93. />
  94. <RotateHandle x={minX + width / 2} y={minY - cp * 2} r={cp / 2} />
  95. </g>
  96. )
  97. }
  98. function RotateHandle({ x, y, r }: { x: number; y: number; r: number }) {
  99. const rRotateHandle = useRef<SVGCircleElement>(null)
  100. return (
  101. <StyledRotateHandle
  102. ref={rRotateHandle}
  103. cx={x}
  104. cy={y}
  105. r={r}
  106. onPointerDown={(e) => {
  107. e.stopPropagation()
  108. rRotateHandle.current.setPointerCapture(e.pointerId)
  109. state.send("POINTED_ROTATE_HANDLE", inputs.pointerDown(e, "rotate"))
  110. }}
  111. onPointerUp={(e) => {
  112. e.stopPropagation()
  113. rRotateHandle.current.releasePointerCapture(e.pointerId)
  114. rRotateHandle.current.replaceWith(rRotateHandle.current)
  115. state.send("STOPPED_POINTING", inputs.pointerDown(e, "rotate"))
  116. }}
  117. />
  118. )
  119. }
  120. function Corner({
  121. x,
  122. y,
  123. width,
  124. height,
  125. corner,
  126. }: {
  127. x: number
  128. y: number
  129. width: number
  130. height: number
  131. corner: TransformCorner
  132. }) {
  133. const rCorner = useRef<SVGRectElement>(null)
  134. return (
  135. <g>
  136. <StyledCorner
  137. ref={rCorner}
  138. x={x + width * -0.5}
  139. y={y + height * -0.5}
  140. width={width}
  141. height={height}
  142. corner={corner}
  143. onPointerDown={(e) => {
  144. e.stopPropagation()
  145. rCorner.current.setPointerCapture(e.pointerId)
  146. state.send("POINTED_BOUNDS_CORNER", inputs.pointerDown(e, corner))
  147. }}
  148. onPointerUp={(e) => {
  149. e.stopPropagation()
  150. rCorner.current.releasePointerCapture(e.pointerId)
  151. rCorner.current.replaceWith(rCorner.current)
  152. state.send("STOPPED_POINTING", inputs.pointerDown(e, corner))
  153. }}
  154. />
  155. </g>
  156. )
  157. }
  158. function EdgeHorizontal({
  159. x,
  160. y,
  161. width,
  162. height,
  163. edge,
  164. }: {
  165. x: number
  166. y: number
  167. width: number
  168. height: number
  169. edge: TransformEdge.Top | TransformEdge.Bottom
  170. }) {
  171. const rEdge = useRef<SVGRectElement>(null)
  172. return (
  173. <StyledEdge
  174. ref={rEdge}
  175. x={x}
  176. y={y - height / 2}
  177. width={width}
  178. height={height}
  179. onPointerDown={(e) => {
  180. e.stopPropagation()
  181. rEdge.current.setPointerCapture(e.pointerId)
  182. state.send("POINTED_BOUNDS_EDGE", inputs.pointerDown(e, edge))
  183. }}
  184. onPointerUp={(e) => {
  185. e.stopPropagation()
  186. e.preventDefault()
  187. state.send("STOPPED_POINTING", inputs.pointerUp(e))
  188. rEdge.current.releasePointerCapture(e.pointerId)
  189. rEdge.current.replaceWith(rEdge.current)
  190. }}
  191. edge={edge}
  192. />
  193. )
  194. }
  195. function EdgeVertical({
  196. x,
  197. y,
  198. width,
  199. height,
  200. edge,
  201. }: {
  202. x: number
  203. y: number
  204. width: number
  205. height: number
  206. edge: TransformEdge.Right | TransformEdge.Left
  207. }) {
  208. const rEdge = useRef<SVGRectElement>(null)
  209. return (
  210. <StyledEdge
  211. ref={rEdge}
  212. x={x - width / 2}
  213. y={y}
  214. width={width}
  215. height={height}
  216. onPointerDown={(e) => {
  217. e.stopPropagation()
  218. state.send("POINTED_BOUNDS_EDGE", inputs.pointerDown(e, edge))
  219. rEdge.current.setPointerCapture(e.pointerId)
  220. }}
  221. onPointerUp={(e) => {
  222. e.stopPropagation()
  223. state.send("STOPPED_POINTING", inputs.pointerUp(e))
  224. rEdge.current.releasePointerCapture(e.pointerId)
  225. rEdge.current.replaceWith(rEdge.current)
  226. }}
  227. edge={edge}
  228. />
  229. )
  230. }
  231. const StyledEdge = styled("rect", {
  232. stroke: "none",
  233. fill: "none",
  234. variants: {
  235. edge: {
  236. bottom_edge: { cursor: "ns-resize" },
  237. right_edge: { cursor: "ew-resize" },
  238. top_edge: { cursor: "ns-resize" },
  239. left_edge: { cursor: "ew-resize" },
  240. },
  241. },
  242. })
  243. const StyledCorner = styled("rect", {
  244. stroke: "$bounds",
  245. fill: "#fff",
  246. zStrokeWidth: 2,
  247. variants: {
  248. corner: {
  249. top_left_corner: { cursor: "nwse-resize" },
  250. top_right_corner: { cursor: "nesw-resize" },
  251. bottom_right_corner: { cursor: "nwse-resize" },
  252. bottom_left_corner: { cursor: "nesw-resize" },
  253. },
  254. },
  255. })
  256. const StyledRotateHandle = styled("circle", {
  257. stroke: "$bounds",
  258. fill: "#fff",
  259. zStrokeWidth: 2,
  260. cursor: "grab",
  261. })
  262. const StyledBounds = styled("rect", {
  263. fill: "none",
  264. stroke: "$bounds",
  265. zStrokeWidth: 2,
  266. })