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

bounds.tsx 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. import state, { useSelector } from "state"
  2. import { motion } from "framer-motion"
  3. import styled from "styles"
  4. import inputs from "state/inputs"
  5. export default function Bounds() {
  6. const bounds = useSelector((state) => state.values.selectedBounds)
  7. const isBrushing = useSelector((state) => state.isIn("brushSelecting"))
  8. const zoom = useSelector((state) => state.data.camera.zoom)
  9. if (!bounds) return null
  10. const { minX, minY, maxX, maxY, width, height } = bounds
  11. const p = 4 / zoom
  12. const cp = p * 2
  13. return (
  14. <g pointerEvents={isBrushing ? "none" : "all"}>
  15. <StyledBounds
  16. x={minX}
  17. y={minY}
  18. width={width}
  19. height={height}
  20. pointerEvents="none"
  21. />
  22. {width * zoom > 8 && (
  23. <>
  24. <Corner
  25. x={minX}
  26. y={minY}
  27. corner={0}
  28. width={cp}
  29. height={cp}
  30. cursor="nwse-resize"
  31. />
  32. <Corner
  33. x={maxX}
  34. y={minY}
  35. corner={1}
  36. width={cp}
  37. height={cp}
  38. cursor="nesw-resize"
  39. />
  40. <Corner
  41. x={maxX}
  42. y={maxY}
  43. corner={2}
  44. width={cp}
  45. height={cp}
  46. cursor="nwse-resize"
  47. />
  48. <Corner
  49. x={minX}
  50. y={maxY}
  51. corner={3}
  52. width={cp}
  53. height={cp}
  54. cursor="nesw-resize"
  55. />
  56. </>
  57. )}
  58. <EdgeHorizontal
  59. x={minX + p}
  60. y={minY}
  61. width={Math.max(0, width - p * 2)}
  62. height={p}
  63. onSelect={(e) => {
  64. e.stopPropagation()
  65. if (e.buttons !== 1) return
  66. state.send("POINTED_BOUNDS_EDGE", {
  67. edge: 0,
  68. ...inputs.pointerDown(e),
  69. })
  70. document.body.style.cursor = "ns-resize"
  71. }}
  72. />
  73. <EdgeVertical
  74. x={maxX}
  75. y={minY + p}
  76. width={p}
  77. height={Math.max(0, height - p * 2)}
  78. onSelect={(e) => {
  79. e.stopPropagation()
  80. if (e.buttons !== 1) return
  81. state.send("POINTED_BOUNDS_EDGE", {
  82. edge: 1,
  83. ...inputs.pointerDown(e),
  84. })
  85. document.body.style.cursor = "ew-resize"
  86. }}
  87. />
  88. <EdgeHorizontal
  89. x={minX + p}
  90. y={maxY}
  91. width={Math.max(0, width - p * 2)}
  92. height={p}
  93. onSelect={(e) => {
  94. e.stopPropagation()
  95. if (e.buttons !== 1) return
  96. state.send("POINTED_BOUNDS_EDGE", {
  97. edge: 2,
  98. ...inputs.pointerDown(e),
  99. })
  100. document.body.style.cursor = "ns-resize"
  101. }}
  102. />
  103. <EdgeVertical
  104. x={minX}
  105. y={minY + p}
  106. width={p}
  107. height={Math.max(0, height - p * 2)}
  108. onSelect={(e) => {
  109. e.stopPropagation()
  110. if (e.buttons !== 1) return
  111. state.send("POINTED_BOUNDS_EDGE", {
  112. edge: 3,
  113. ...inputs.pointerDown(e),
  114. })
  115. document.body.style.cursor = "ew-resize"
  116. }}
  117. />
  118. </g>
  119. )
  120. }
  121. function Corner({
  122. x,
  123. y,
  124. width,
  125. height,
  126. cursor,
  127. onHover,
  128. corner,
  129. }: {
  130. x: number
  131. y: number
  132. width: number
  133. height: number
  134. cursor: string
  135. corner: number
  136. onHover?: () => void
  137. }) {
  138. const isTop = corner === 0 || corner === 1
  139. const isLeft = corner === 0 || corner === 3
  140. return (
  141. <g>
  142. <motion.rect
  143. x={x + width * (isLeft ? -1.25 : -0.5)} // + width * 2 * transformOffset[0]}
  144. y={y + width * (isTop ? -1.25 : -0.5)} // + height * 2 * transformOffset[1]}
  145. width={width * 1.75}
  146. height={height * 1.75}
  147. onPanEnd={restoreCursor}
  148. onTap={restoreCursor}
  149. onPointerDown={(e) => {
  150. e.stopPropagation()
  151. if (e.buttons !== 1) return
  152. state.send("POINTED_ROTATE_CORNER", {
  153. corner,
  154. ...inputs.pointerDown(e),
  155. })
  156. document.body.style.cursor = "grabbing"
  157. }}
  158. style={{ cursor: "grab" }}
  159. fill="transparent"
  160. />
  161. <StyledCorner
  162. x={x + width * -0.5}
  163. y={y + height * -0.5}
  164. width={width}
  165. height={height}
  166. onPointerEnter={onHover}
  167. onPointerDown={(e) => {
  168. e.stopPropagation()
  169. if (e.buttons !== 1) return
  170. state.send("POINTED_BOUNDS_CORNER", {
  171. corner,
  172. ...inputs.pointerDown(e),
  173. })
  174. document.body.style.cursor = "nesw-resize"
  175. }}
  176. onPanEnd={restoreCursor}
  177. onTap={restoreCursor}
  178. style={{ cursor }}
  179. />
  180. </g>
  181. )
  182. }
  183. function EdgeHorizontal({
  184. x,
  185. y,
  186. width,
  187. height,
  188. onHover,
  189. onSelect,
  190. }: {
  191. x: number
  192. y: number
  193. width: number
  194. height: number
  195. onHover?: () => void
  196. onSelect?: (e: React.PointerEvent) => void
  197. }) {
  198. return (
  199. <StyledEdge
  200. x={x}
  201. y={y - height / 2}
  202. width={width}
  203. height={height}
  204. onPointerEnter={onHover}
  205. onPointerDown={onSelect}
  206. onPanEnd={restoreCursor}
  207. onTap={restoreCursor}
  208. style={{ cursor: "ns-resize" }}
  209. direction="horizontal"
  210. />
  211. )
  212. }
  213. function EdgeVertical({
  214. x,
  215. y,
  216. width,
  217. height,
  218. onHover,
  219. onSelect,
  220. }: {
  221. x: number
  222. y: number
  223. width: number
  224. height: number
  225. onHover?: () => void
  226. onSelect?: (e: React.PointerEvent) => void
  227. }) {
  228. return (
  229. <StyledEdge
  230. x={x - width / 2}
  231. y={y}
  232. width={width}
  233. height={height}
  234. onPointerEnter={onHover}
  235. onPointerDown={onSelect}
  236. onPanEnd={restoreCursor}
  237. onTap={restoreCursor}
  238. direction="vertical"
  239. />
  240. )
  241. }
  242. function restoreCursor(e: PointerEvent) {
  243. state.send("STOPPED_POINTING", { id: "bounds", ...inputs.pointerUp(e) })
  244. document.body.style.cursor = "default"
  245. }
  246. const StyledEdge = styled(motion.rect, {
  247. stroke: "none",
  248. fill: "none",
  249. variant: {
  250. direction: {
  251. horizontal: { cursor: "ns-resize" },
  252. vertical: { cursor: "ew-resize" },
  253. },
  254. },
  255. })
  256. const StyledCorner = styled(motion.rect, {
  257. stroke: "$bounds",
  258. fill: "#fff",
  259. zStrokeWidth: 2,
  260. })
  261. const StyledBounds = styled("rect", {
  262. fill: "none",
  263. stroke: "$bounds",
  264. zStrokeWidth: 2,
  265. })