diff --git a/README.md b/README.md index 3728a12..3b74c08 100644 --- a/README.md +++ b/README.md @@ -169,11 +169,11 @@ The options object is optional, as are each of its properties. The `start` and `end` options accept an object: -| Property | Type | Default | Description | -| -------- | -------- | ------- | ------------------------------------------- | -| `cap` | boolean | true | Whether to draw a cap. | -| `taper` | number | 0 | The distance to taper. | -| `easing` | function | t => t | An easing function for the tapering effect. | +| Property | Type | Default | Description | +| -------- | ----------------- | ------- | ---------------------------------------------------------------------------------------- | +| `cap` | boolean | true | Whether to draw a cap. | +| `taper` | number or boolean | 0 | The distance to taper. If set to true, the taper will be the total length of the stroke. | +| `easing` | function | t => t | An easing function for the tapering effect. | **Note:** The `cap` property has no effect when `taper` is more than zero. diff --git a/package.json b/package.json index 2674286..d6c05a8 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,6 @@ "@testing-library/react": "^12.0.0", "@types/jest": "^27.0.1", "@types/node": "^15.0.1", - "@types/react": "^17.0.19", - "@types/react-dom": "^17.0.9", "@typescript-eslint/eslint-plugin": "^4.19.0", "@typescript-eslint/parser": "^4.19.0", "babel-jest": "^27.1.0", @@ -49,8 +47,6 @@ "fake-indexeddb": "^3.1.3", "jest": "^27.1.0", "lerna": "^3.15.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", "ts-jest": "^27.0.5", "tslib": "^2.3.0", "typedoc": "^0.21.9", diff --git a/packages/dev/package.json b/packages/dev/package.json index 3856870..8c961be 100644 --- a/packages/dev/package.json +++ b/packages/dev/package.json @@ -27,9 +27,7 @@ "files": [ "src" ], - "dependencies": { - "@types/react-dom": "^17.0.11" - }, + "dependencies": {}, "devDependencies": { "@modulz/radix-icons": "^4.0.0", "@radix-ui/react-checkbox": "^0.1.0", @@ -40,8 +38,8 @@ "@tldraw/intersect": "^0.1.3", "@tldraw/vec": "^0.1.3", "@types/node": "^14.14.35", - "@types/react": "^17.0.34", - "@types/react-dom": "^17.0.9", + "@types/react": "^18.0.10", + "@types/react-dom": "^18.0.5", "concurrently": "6.0.1", "create-serve": "1.0.1", "css-tree": "^1.1.3", @@ -50,12 +48,13 @@ "husky": "^7.0.0", "jest": "^27.0.6", "perfect-freehand": "^1.0.16", - "react": "^17.0.2", - "react-dom": "^17.0.2", "react-hotkeys-hook": "^3.4.0", "rimraf": "3.0.2", "rko": "^0.5.25", "tslib": "^2.3.1", - "typescript": "^4.3.5" + "typescript": "^4.3.5", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "zustand": "^4.0.0-rc.1" } -} +} \ No newline at end of file diff --git a/packages/dev/src/components/controls/controls.module.css b/packages/dev/src/components/controls/controls.module.css index 0f91335..f185b09 100644 --- a/packages/dev/src/components/controls/controls.module.css +++ b/packages/dev/src/components/controls/controls.module.css @@ -19,7 +19,7 @@ .inputs { display: grid; - grid-template-columns: auto 1fr auto; + grid-template-columns: 96px 1fr auto; grid-auto-rows: 32px; align-items: center; column-gap: 16px; diff --git a/packages/dev/src/components/controls/controls.tsx b/packages/dev/src/components/controls/controls.tsx index 61450ee..199dfb3 100644 --- a/packages/dev/src/components/controls/controls.tsx +++ b/packages/dev/src/components/controls/controls.tsx @@ -101,7 +101,7 @@ export function Controls() { }, []) const handleTaperStartChange = React.useCallback((v: number[]) => { - app.patchStyleForAllShapes({ taperStart: v[0] }) + app.patchStyleForAllShapes({ taperStart: v[0] === 100 ? true : v[0] }) }, []) const handleEasingStartChange = React.useCallback((easing: string) => { @@ -120,7 +120,7 @@ export function Controls() { }, []) const handleTaperEndChange = React.useCallback((v: number[]) => { - app.patchStyleForAllShapes({ taperEnd: v[0] }) + app.patchStyleForAllShapes({ taperEnd: v[0] === 100 ? true : v[0] }) }, []) const handleEasingEndChange = React.useCallback((easing: string) => { @@ -199,7 +199,7 @@ export function Controls() { {style.taperEnd <= 0 && ( void onPointerUp: () => void onDoubleClick: () => void + label?: string } export function Slider({ @@ -24,6 +25,7 @@ export function Slider({ min, max, step, + label, value = [0], ...props }: SliderProps) { @@ -60,15 +62,19 @@ export function Slider({ ))} - + {label ? ( + {label} + ) : ( + + )} ) } diff --git a/packages/dev/src/state/shapes/draw.tsx b/packages/dev/src/state/shapes/draw.tsx index 9ac156b..35bbbcc 100644 --- a/packages/dev/src/state/shapes/draw.tsx +++ b/packages/dev/src/state/shapes/draw.tsx @@ -25,7 +25,7 @@ export class DrawUtil extends TLShapeUtil { rotatedCache = new WeakMap([]) - strokeCache = new WeakMap([]) + strokeCache = new WeakMap([]) getShape = (props: Partial): T => { return Utils.deepMerge( @@ -85,25 +85,26 @@ export class DrawUtil extends TLShapeUtil { const simulatePressure = shape.points[2]?.[2] === 0.5 - const outlinePoints = Utils.getFromCache( - this.strokeCache, - shape.points, - () => - getStroke(shape.points, { - size, - thinning, - streamline, - easing: EASINGS[easing], - smoothing, - start: { - taper: taperStart, - cap: capStart, - easing: EASINGS[easingStart], - }, - end: { taper: taperEnd, cap: capEnd, easing: EASINGS[easingEnd] }, - simulatePressure, - last: isDone, - }) + const outlinePoints = Utils.getFromCache(this.strokeCache, shape, () => + getStroke(shape.points, { + size, + thinning, + streamline, + easing: EASINGS[easing], + smoothing, + start: { + taper: taperStart, + cap: capStart, + easing: EASINGS[easingStart], + }, + end: { + taper: taperEnd, + cap: capEnd, + easing: EASINGS[easingEnd], + }, + simulatePressure, + last: isDone, + }) ) const drawPathData = getSvgPathFromStroke(outlinePoints) diff --git a/packages/dev/src/state/state.ts b/packages/dev/src/state/state.ts index 26700fb..76c5de8 100644 --- a/packages/dev/src/state/state.ts +++ b/packages/dev/src/state/state.ts @@ -481,7 +481,7 @@ export class AppState extends StateManager { } if (Utils.pointInBounds(pt, bounds)) { - const points = draw.strokeCache.get(shape.points) + const points = draw.strokeCache.get(shape) if ( (points && diff --git a/packages/dev/src/types.ts b/packages/dev/src/types.ts index e34e9f3..5af778f 100644 --- a/packages/dev/src/types.ts +++ b/packages/dev/src/types.ts @@ -21,8 +21,8 @@ export interface DrawStyles { thinning: number streamline: number smoothing: number - taperStart: number - taperEnd: number + taperStart: number | boolean + taperEnd: number | boolean capStart: boolean capEnd: boolean easingStart: Easing diff --git a/packages/perfect-freehand/README.md b/packages/perfect-freehand/README.md index 3728a12..3b74c08 100644 --- a/packages/perfect-freehand/README.md +++ b/packages/perfect-freehand/README.md @@ -169,11 +169,11 @@ The options object is optional, as are each of its properties. The `start` and `end` options accept an object: -| Property | Type | Default | Description | -| -------- | -------- | ------- | ------------------------------------------- | -| `cap` | boolean | true | Whether to draw a cap. | -| `taper` | number | 0 | The distance to taper. | -| `easing` | function | t => t | An easing function for the tapering effect. | +| Property | Type | Default | Description | +| -------- | ----------------- | ------- | ---------------------------------------------------------------------------------------- | +| `cap` | boolean | true | Whether to draw a cap. | +| `taper` | number or boolean | 0 | The distance to taper. If set to true, the taper will be the total length of the stroke. | +| `easing` | function | t => t | An easing function for the tapering effect. | **Note:** The `cap` property has no effect when `taper` is more than zero. diff --git a/packages/perfect-freehand/src/getStrokeOutlinePoints.ts b/packages/perfect-freehand/src/getStrokeOutlinePoints.ts index 4ca9f82..0cf2706 100644 --- a/packages/perfect-freehand/src/getStrokeOutlinePoints.ts +++ b/packages/perfect-freehand/src/getStrokeOutlinePoints.ts @@ -52,17 +52,11 @@ export function getStrokeOutlinePoints( last: isComplete = false, } = options - const { - cap: capStart = true, - taper: taperStart = 0, - easing: taperStartEase = (t) => t * (2 - t), - } = start + let { cap: capStart = true, easing: taperStartEase = (t) => t * (2 - t) } = + start - const { - cap: capEnd = true, - taper: taperEnd = 0, - easing: taperEndEase = (t) => --t * t * t + 1, - } = end + let { cap: capEnd = true, easing: taperEndEase = (t) => --t * t * t + 1 } = + end // We can't do anything with an empty array or a stroke with negative size. if (points.length === 0 || size <= 0) { @@ -72,6 +66,20 @@ export function getStrokeOutlinePoints( // The total length of the line const totalLength = points[points.length - 1].runningLength + const taperStart = + start.taper === false + ? 0 + : start.taper === true + ? Math.max(size, totalLength) + : (start.taper as number) + + const taperEnd = + end.taper === false + ? 0 + : end.taper === true + ? Math.max(size, totalLength) + : (end.taper as number) + // The minimum allowed distance between points (squared) const minDistance = Math.pow(size * smoothing, 2) diff --git a/packages/perfect-freehand/src/types.ts b/packages/perfect-freehand/src/types.ts index 3c226f5..d2ec767 100644 --- a/packages/perfect-freehand/src/types.ts +++ b/packages/perfect-freehand/src/types.ts @@ -20,12 +20,12 @@ export interface StrokeOptions { simulatePressure?: boolean start?: { cap?: boolean - taper?: number + taper?: number | boolean easing?: (distance: number) => number } end?: { cap?: boolean - taper?: number + taper?: number | boolean easing?: (distance: number) => number } // Whether to handle the points as a completed stroke. diff --git a/yarn.lock b/yarn.lock index 6e1b147..d6f9bb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2467,21 +2467,14 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== -"@types/react-dom@^17.0.11": - version "17.0.11" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466" - integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q== +"@types/react-dom@^18.0.5": + version "18.0.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a" + integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA== dependencies: "@types/react" "*" -"@types/react-dom@^17.0.9": - version "17.0.9" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add" - integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^17.0.19": +"@types/react@*": version "17.0.19" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.19.tgz#8f2a85e8180a43b57966b237d26a29481dacc991" integrity sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A== @@ -2490,10 +2483,10 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@^17.0.34": - version "17.0.34" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102" - integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg== +"@types/react@^18.0.10": + version "18.0.10" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.10.tgz#5692944d4a45e204fb7a981eb1388afe919cf4d0" + integrity sha512-dIugadZuIPrRzvIEevIu7A1smqOAjkSMv8qOfwPt9Ve6i6JT/FQcCHyk2qIAxwsQNKZt5/oGR0T4z9h2dXRAkg== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -6968,7 +6961,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -7617,14 +7610,13 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -react-dom@^17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" - integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== +react-dom@^18.1.0: + version "18.1.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.1.0.tgz#7f6dd84b706408adde05e1df575b3a024d7e8a2f" + integrity sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler "^0.20.2" + scheduler "^0.22.0" react-hotkeys-hook@^3.4.0: version "3.4.0" @@ -7638,13 +7630,12 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react@^17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" - integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== +react@^18.1.0: + version "18.1.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890" + integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" read-cmd-shim@^1.0.1: version "1.0.5" @@ -8064,13 +8055,12 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -scheduler@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" - integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== +scheduler@^0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8" + integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" @@ -9120,6 +9110,11 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +use-sync-external-store@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82" + integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -9473,3 +9468,10 @@ zustand@^3.5.9: version "3.5.10" resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.5.10.tgz#d2622efd64530ffda285ee5b13ff645b68ab0faf" integrity sha512-upluvSRWrlCiExu2UbkuMIPJ9AigyjRFoO7O9eUossIj7rPPq7pcJ0NKk6t2P7KF80tg/UdPX6/pNKOSbs9DEg== + +zustand@^4.0.0-rc.1: + version "4.0.0-rc.1" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.0.0-rc.1.tgz#ec30a3afc03728adec7e1bd7bcc3592176372201" + integrity sha512-qgcs7zLqBdHu0PuT3GW4WCIY5SgXdsv30GQMu9Qpp1BA2aS+sNS8l4x0hWuyEhjXkN+701aGWawhKDv6oWJAcw== + dependencies: + use-sync-external-store "1.1.0"