|
1 | | -from solid-js import { createMemo, For, type JSX } |
| 1 | +from solid-js import { createEffect, createMemo, createSignal, For, type JSX } |
2 | 2 | operator { max } := Math |
3 | 3 | from @solid-primitives/tween import { createTween } |
4 | 4 |
|
@@ -167,6 +167,12 @@ function ceilLog2(glues: number): number |
167 | 167 | maxVal <<= 1 |
168 | 168 | nBits |
169 | 169 |
|
| 170 | +function svgPoint(svg: SVGSVGElement, x: number, y: number): DOMPoint |
| 171 | + pt := svg.createSVGPoint() |
| 172 | + pt.x = x |
| 173 | + pt.y = y |
| 174 | + pt.matrixTransform svg.getScreenCTM()!.inverse() |
| 175 | + |
170 | 176 | export function unsignedToSigned(tiles: Prototile[], glues: number): {tiles: Prototile[], glues: number} |
171 | 177 | // Remove aliasing |
172 | 178 | tiles = for each tile of tiles |
@@ -226,12 +232,57 @@ export function ThreeTiles(props: {tiles: Prototile[], glues: number, zoom: numb |
226 | 232 | ease: (t) => 0.5 - Math.cos(Math.PI * t) / 2 |
227 | 233 | center := createMemo => |
228 | 234 | `${polys().wheelPoly.maxX()/2} ${polys().wheelPoly.maxY()/2}` |
| 235 | + width := createMemo => polys().shurikenPoly.maxX() + 2*margin |
| 236 | + height := createMemo => polys().shurikenPoly.maxY() + 2*margin |
| 237 | + |
| 238 | + [panX, setPanX] := createSignal 0 |
| 239 | + [panY, setPanY] := createSignal 0 |
| 240 | + createEffect => |
| 241 | + // Keep pan within bounds |
| 242 | + if panX() >= 0 |
| 243 | + setPanX 0 |
| 244 | + else |
| 245 | + if width() - panX() > width() * props.zoom |
| 246 | + setPanX -(width() * props.zoom - width()) |
| 247 | + if panY() >= 0 |
| 248 | + setPanY 0 |
| 249 | + else |
| 250 | + if height() - panY() > height() * props.zoom |
| 251 | + setPanY -(height() * props.zoom - height()) |
| 252 | + starts: Record<number, { |
| 253 | + panX: number |
| 254 | + panY: number |
| 255 | + start: {x: number, y: number} |
| 256 | + }> := {} |
229 | 257 |
|
230 | | - <svg viewBox=`${-margin} ${-margin} ${polys().shurikenPoly.maxX() + 2*margin} ${polys().shurikenPoly.maxY() + 2*margin}` width=`${props.zoom*100}%`> |
231 | | - <Polygon .ptile turtle=polys().staplePoly> |
232 | | - <g transform=`rotate(${angle()} ${center()})`> |
233 | | - <Polygon .ptile turtle=polys().wheelPoly> |
234 | | - <For each=polys().gluePolys> |
235 | | - (poly) => |
236 | | - <Polygon .pglue turtle=poly> |
237 | | - <Polygon .ptile turtle=polys().shurikenPoly> |
| 258 | + let svg!: SVGSVGElement |
| 259 | + <svg ref=svg viewBox=`${-margin} ${-margin} ${width()} ${height()}` |
| 260 | + onPointerDown= |
| 261 | + (e) => |
| 262 | + starts[e.pointerId] = { |
| 263 | + panX(), panY() |
| 264 | + start: svgPoint svg, e.clientX, e.clientY |
| 265 | + } |
| 266 | + svg.setPointerCapture e.pointerId |
| 267 | + onPointerMove= |
| 268 | + (e) => |
| 269 | + return unless starts[e.pointerId]? |
| 270 | + current := svgPoint svg, e.clientX, e.clientY |
| 271 | + {panX, panY, start} := starts[e.pointerId] |
| 272 | + setPanX panX + (current.x - start.x) |
| 273 | + setPanY panY + (current.y - start.y) |
| 274 | + onPointerUp= |
| 275 | + (e) => |
| 276 | + delete starts[e.pointerId] |
| 277 | + onPointerLeave= |
| 278 | + (e) => |
| 279 | + delete starts[e.pointerId] |
| 280 | + > |
| 281 | + <g transform=`translate(${panX()} ${panY()}) scale(${props.zoom})`> |
| 282 | + <Polygon .ptile turtle=polys().staplePoly> |
| 283 | + <g transform=`rotate(${angle()} ${center()})`> |
| 284 | + <Polygon .ptile turtle=polys().wheelPoly> |
| 285 | + <For each=polys().gluePolys> |
| 286 | + (poly) => |
| 287 | + <Polygon .pglue turtle=poly> |
| 288 | + <Polygon .ptile turtle=polys().shurikenPoly> |
0 commit comments