您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

bounds.tsx 6.3KB

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