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.

bounds.tsx 6.0KB

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