tldraw/packages/editor/src/lib/components/GeometryDebuggingView.tsx
Steve Ruiz ac149c1014
Dynamic size mode + fill fill (#3835)
This PR adds a user preference for "dynamic size mode" where the scale
of shapes (text size, stroke width) is relative to the current zoom
level. This means that the stroke width in screen pixels (or text size
in screen pixels) is identical regardless of zoom level.

![Kapture 2024-05-27 at 05 23
21](https://github.com/tldraw/tldraw/assets/23072548/f247ecce-bfcd-4f85-b7a5-d7677b38e4d8)

- [x] Draw shape
- [x] Text shape
- [x] Highlighter shape
- [x] Geo shape
- [x] Arrow shape
- [x] Note shape
- [x] Line shape

Embed shape?

### Change Type

- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature

### Test Plan

1. Use the tools.
2. Change zoom

- [ ] Unit Tests

### Release Notes

- Adds a dynamic size user preferences.
- Removes double click to reset scale on text shapes.
- Removes double click to reset autosize on text shapes.

---------

Co-authored-by: Taha <98838967+Taha-Hassan-Git@users.noreply.github.com>
Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com>
2024-06-16 16:58:13 +00:00

126 lines
3.2 KiB
TypeScript

import { track } from '@tldraw/state'
import { modulate } from '@tldraw/utils'
import { useEffect, useState } from 'react'
import { useEditor } from '../hooks/useEditor'
import { Geometry2d } from '../primitives/geometry/Geometry2d'
import { Group2d } from '../primitives/geometry/Group2d'
function useTick(isEnabled = true) {
const [_, setTick] = useState(0)
const editor = useEditor()
useEffect(() => {
if (!isEnabled) return
const update = () => setTick((tick) => tick + 1)
editor.on('tick', update)
return () => {
editor.off('tick', update)
}
}, [editor, isEnabled])
}
export const GeometryDebuggingView = track(function GeometryDebuggingView({
showStroke = true,
showVertices = true,
showClosestPointOnOutline = true,
}: {
showStroke?: boolean
showVertices?: boolean
showClosestPointOnOutline?: boolean
}) {
const editor = useEditor()
useTick(showClosestPointOnOutline)
const zoomLevel = editor.getZoomLevel()
const renderingShapes = editor.getRenderingShapes()
const {
inputs: { currentPagePoint },
} = editor
return (
<svg
style={{
position: 'absolute',
pointerEvents: 'none',
zIndex: 999999999,
top: 0,
left: 0,
overflow: 'visible',
}}
>
{renderingShapes.map((result) => {
const shape = editor.getShape(result.id)!
if (shape.type === 'group') return null
const geometry = editor.getShapeGeometry(shape)
const pageTransform = editor.getShapePageTransform(shape)!
const pointInShapeSpace = editor.getPointInShapeSpace(shape, currentPagePoint)
const nearestPointOnShape = geometry.nearestPoint(pointInShapeSpace)
const distanceToPoint = geometry.distanceToPoint(pointInShapeSpace, true)
const dist = Math.abs(distanceToPoint) * zoomLevel
const hitInside = distanceToPoint < 0
const { vertices } = geometry
return (
<g
key={result.id + '_outline'}
transform={pageTransform.toCssString()}
strokeLinecap="round"
strokeLinejoin="round"
>
{showStroke && (
<g
stroke={geometry.debugColor ?? 'red'}
opacity="1"
strokeWidth={2 / zoomLevel}
fill="none"
>
<GeometryStroke geometry={geometry} />
</g>
)}
{showVertices &&
vertices.map((v, i) => (
<circle
key={`v${i}`}
cx={v.x}
cy={v.y}
r={2 / zoomLevel}
fill={`hsl(${modulate(i, [0, vertices.length - 1], [120, 200])}, 100%, 50%)`}
stroke="black"
strokeWidth={1 / zoomLevel}
/>
))}
{showClosestPointOnOutline && dist < 150 && (
<line
x1={nearestPointOnShape.x}
y1={nearestPointOnShape.y}
x2={pointInShapeSpace.x}
y2={pointInShapeSpace.y}
opacity={1 - dist / 150}
stroke={hitInside ? 'goldenrod' : 'dodgerblue'}
strokeWidth={2 / zoomLevel}
/>
)}
</g>
)
})}
</svg>
)
})
function GeometryStroke({ geometry }: { geometry: Geometry2d }) {
if (geometry instanceof Group2d) {
return (
<>
{[...geometry.children, ...geometry.ignoredChildren].map((child, i) => (
<GeometryStroke geometry={child} key={i} />
))}
</>
)
}
return <path d={geometry.toSimpleSvgPath()} />
}