[fix] indicator shapes (#121)
* Fix indicator shape for draw shape, rectangle shape * Fixes perfect-freehand bug * Tweaks streamline
This commit is contained in:
parent
5d3af9cec0
commit
31638c7c90
12 changed files with 198 additions and 78 deletions
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Chris Hager
|
||||
Copyright (c) 2021 Stephen Ruiz Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
21
packages/core/LICENSE
Normal file
21
packages/core/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Stephen Ruiz Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1656,7 +1656,7 @@ left past the initial left edge) then swap points on that axis.
|
|||
* Turn an array of points into a path of quadradic curves.
|
||||
* @param stroke ;
|
||||
*/
|
||||
static getSvgPathFromStroke(points: number[][]): string {
|
||||
static getSvgPathFromStroke(points: number[][], closed = true): string {
|
||||
if (!points.length) {
|
||||
return ''
|
||||
}
|
||||
|
@ -1667,7 +1667,9 @@ left past the initial left edge) then swap points on that axis.
|
|||
.reduce(
|
||||
(acc, point, i, arr) => {
|
||||
if (i === max) {
|
||||
acc.push('Z')
|
||||
if (closed) {
|
||||
acc.push('Z')
|
||||
}
|
||||
} else {
|
||||
acc.push(point, Vec.med(point, arr[i + 1]))
|
||||
}
|
||||
|
|
21
packages/dev/LICENSE
Normal file
21
packages/dev/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Stephen Ruiz Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
21
packages/intersect/LICENSE
Normal file
21
packages/intersect/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Stephen Ruiz Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
21
packages/tldraw/LICENSE
Normal file
21
packages/tldraw/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Stephen Ruiz Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -67,9 +67,9 @@
|
|||
"@tldraw/core": "^0.0.102",
|
||||
"@tldraw/intersect": "^0.0.102",
|
||||
"@tldraw/vec": "^0.0.102",
|
||||
"perfect-freehand": "^1.0.9",
|
||||
"perfect-freehand": "^1.0.12",
|
||||
"react-hotkeys-hook": "^3.4.0",
|
||||
"rko": "^0.5.25"
|
||||
},
|
||||
"gitHead": "5cb031ddc264846ec6732d7179511cddea8ef034"
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import * as React from 'react'
|
|||
import { SVGContainer, TLBounds, Utils, TLTransformInfo, ShapeUtil } from '@tldraw/core'
|
||||
import { Vec } from '@tldraw/vec'
|
||||
import { intersectBoundsBounds, intersectBoundsPolyline } from '@tldraw/intersect'
|
||||
import getStroke, { getStrokePoints } from 'perfect-freehand'
|
||||
import getStroke, { getStrokeOutlinePoints, getStrokePoints } from 'perfect-freehand'
|
||||
import { defaultStyle, getShapeStyle } from '~shape/shape-styles'
|
||||
import { DrawShape, DashStyle, TLDrawShapeType, TLDrawToolType, TLDrawMeta } from '~types'
|
||||
import { EASINGS } from '~state/utils'
|
||||
|
@ -38,8 +38,8 @@ export const Draw = new ShapeUtil<DrawShape, SVGSVGElement, TLDrawMeta>(() => ({
|
|||
|
||||
const pathData = React.useMemo(() => {
|
||||
return style.dash === DashStyle.Draw
|
||||
? getDrawStrokePath(shape, isEditing)
|
||||
: getSolidStrokePath(shape)
|
||||
? getDrawStrokePathData(shape, isEditing)
|
||||
: getSolidStrokePathData(shape, isEditing)
|
||||
}, [points, style.size, style.dash, isEditing])
|
||||
|
||||
const styles = getShapeStyle(style, meta.isDarkMode)
|
||||
|
@ -89,7 +89,7 @@ export const Draw = new ShapeUtil<DrawShape, SVGSVGElement, TLDrawMeta>(() => ({
|
|||
d={pathData}
|
||||
fill={styles.stroke}
|
||||
stroke={styles.stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeWidth={styles.strokeWidth}
|
||||
strokeLinejoin="round"
|
||||
strokeLinecap="round"
|
||||
pointerEvents="all"
|
||||
|
@ -146,7 +146,7 @@ export const Draw = new ShapeUtil<DrawShape, SVGSVGElement, TLDrawMeta>(() => ({
|
|||
const { points } = shape
|
||||
|
||||
const pathData = React.useMemo(() => {
|
||||
return getSolidStrokePath(shape)
|
||||
return getSolidStrokePathData(shape, false)
|
||||
}, [points])
|
||||
|
||||
const bounds = this.getBounds(shape)
|
||||
|
@ -264,6 +264,8 @@ export const Draw = new ShapeUtil<DrawShape, SVGSVGElement, TLDrawMeta>(() => ({
|
|||
/* Helpers */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
const STREAMLINE = 0.65
|
||||
|
||||
const simulatePressureSettings = {
|
||||
simulatePressure: true,
|
||||
}
|
||||
|
@ -288,21 +290,34 @@ function getFillPath(shape: DrawShape) {
|
|||
thinning: 0.85,
|
||||
end: { taper: +styles.strokeWidth * 10 },
|
||||
start: { taper: +styles.strokeWidth * 10 },
|
||||
last: true,
|
||||
}).map((pt) => pt.point)
|
||||
)
|
||||
}
|
||||
|
||||
function getDrawStrokePath(shape: DrawShape, isEditing: boolean) {
|
||||
function getDrawStrokePoints(shape: DrawShape, isEditing: boolean) {
|
||||
return getStrokePoints(shape.points, {
|
||||
streamline: STREAMLINE,
|
||||
last: !isEditing,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path data for a stroke with the DashStyle.Draw dash style.
|
||||
*/
|
||||
function getDrawStrokePathData(shape: DrawShape, isEditing: boolean) {
|
||||
const styles = getShapeStyle(shape.style)
|
||||
|
||||
if (shape.points.length < 2) return ''
|
||||
|
||||
const options = shape.points[1][2] === 0.5 ? simulatePressureSettings : realPressureSettings
|
||||
|
||||
const stroke = getStroke(shape.points.slice(2), {
|
||||
const strokePoints = getDrawStrokePoints(shape, isEditing)
|
||||
|
||||
const stroke = getStrokeOutlinePoints(strokePoints, {
|
||||
size: 1 + styles.strokeWidth * 1.618,
|
||||
thinning: 0.6,
|
||||
streamline: 0.7,
|
||||
streamline: STREAMLINE,
|
||||
smoothing: 0.5,
|
||||
end: { taper: styles.strokeWidth * 10, easing: EASINGS.easeOutQuad },
|
||||
easing: (t) => Math.sin((t * Math.PI) / 2),
|
||||
|
@ -315,33 +330,17 @@ function getDrawStrokePath(shape: DrawShape, isEditing: boolean) {
|
|||
return path
|
||||
}
|
||||
|
||||
function getSolidStrokePath(shape: DrawShape) {
|
||||
let { points } = shape
|
||||
/**
|
||||
* Get SVG path data for a shape that has a DashStyle other than DashStyles.Draw.
|
||||
*/
|
||||
function getSolidStrokePathData(shape: DrawShape, isEditing: boolean) {
|
||||
const { points } = shape
|
||||
|
||||
let len = points.length
|
||||
if (points.length === 0) return 'M 0 0 L 0 0'
|
||||
|
||||
if (len === 0) return 'M 0 0 L 0 0'
|
||||
if (len < 3) return `M ${points[0][0]} ${points[0][1]}`
|
||||
const strokePoints = getDrawStrokePoints(shape, isEditing).map((pt) => pt.point.slice(0, 2))
|
||||
|
||||
points = getStrokePoints(points).map((pt) => pt.point)
|
||||
|
||||
len = points.length
|
||||
|
||||
const d = points.reduce(
|
||||
(acc, [x0, y0], i, arr) => {
|
||||
if (i === len - 1) {
|
||||
acc.push('L', x0, y0)
|
||||
return acc
|
||||
}
|
||||
|
||||
const [x1, y1] = arr[i + 1]
|
||||
acc.push(x0.toFixed(2), y0.toFixed(2), ((x0 + x1) / 2).toFixed(2), ((y0 + y1) / 2).toFixed(2))
|
||||
return acc
|
||||
},
|
||||
['M', points[0][0], points[0][1], 'Q']
|
||||
)
|
||||
|
||||
const path = d.join(' ').replaceAll(/(\s[0-9]*\.[0-9]{2})([0-9]*)\b/g, '$1')
|
||||
const path = Utils.getSvgPathFromStroke(strokePoints, false)
|
||||
|
||||
return path
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import { Utils, SVGContainer, ShapeUtil } from '@tldraw/core'
|
||||
import { Vec } from '@tldraw/vec'
|
||||
import getStroke from 'perfect-freehand'
|
||||
import getStroke, { getStrokePoints } from 'perfect-freehand'
|
||||
import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles'
|
||||
import { RectangleShape, DashStyle, TLDrawShapeType, TLDrawToolType, TLDrawMeta } from '~types'
|
||||
import { getBoundsRectangle, transformRectangle, transformSingleRectangle } from '../shared'
|
||||
|
@ -37,8 +37,6 @@ export const Rectangle = new ShapeUtil<RectangleShape, SVGSVGElement, TLDrawMeta
|
|||
const styles = getShapeStyle(style, meta.isDarkMode)
|
||||
const strokeWidth = +styles.strokeWidth
|
||||
|
||||
this
|
||||
|
||||
if (style.dash === DashStyle.Draw) {
|
||||
const pathData = Utils.getFromCache(pathCache, shape.size, () => getRectanglePath(shape))
|
||||
|
||||
|
@ -136,26 +134,7 @@ export const Rectangle = new ShapeUtil<RectangleShape, SVGSVGElement, TLDrawMeta
|
|||
},
|
||||
|
||||
Indicator({ shape }) {
|
||||
const {
|
||||
style,
|
||||
size: [width, height],
|
||||
} = shape
|
||||
|
||||
const styles = getShapeStyle(style, false)
|
||||
const strokeWidth = +styles.strokeWidth
|
||||
|
||||
const sw = strokeWidth
|
||||
|
||||
return (
|
||||
<rect
|
||||
x={sw / 2}
|
||||
y={sw / 2}
|
||||
rx={1}
|
||||
ry={1}
|
||||
width={Math.max(1, width - sw)}
|
||||
height={Math.max(1, height - sw)}
|
||||
/>
|
||||
)
|
||||
return <path d={getRectangleIndicatorPathData(shape)} />
|
||||
},
|
||||
|
||||
getBounds(shape) {
|
||||
|
@ -171,7 +150,7 @@ export const Rectangle = new ShapeUtil<RectangleShape, SVGSVGElement, TLDrawMeta
|
|||
/* Helpers */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
function getRectanglePath(shape: RectangleShape) {
|
||||
function getRectangleDrawPoints(shape: RectangleShape) {
|
||||
const styles = getShapeStyle(shape.style)
|
||||
|
||||
const getRandom = Utils.rng(shape.id)
|
||||
|
@ -209,22 +188,36 @@ function getRectanglePath(shape: RectangleShape) {
|
|||
rm
|
||||
)
|
||||
|
||||
const edgeDist = rm % 2 === 0 ? px : py
|
||||
|
||||
const stroke = getStroke(
|
||||
[...lines.flat(), ...lines[0], ...lines[1]].slice(
|
||||
return {
|
||||
points: [...lines.flat(), ...lines[0], ...lines[1]].slice(
|
||||
4,
|
||||
Math.floor((rm % 2 === 0 ? px : py) / -2) + 2
|
||||
),
|
||||
{
|
||||
size: 1 + styles.strokeWidth * 2,
|
||||
thinning: 0.5,
|
||||
end: { taper: edgeDist },
|
||||
start: { taper: edgeDist },
|
||||
simulatePressure: false,
|
||||
last: true,
|
||||
}
|
||||
)
|
||||
edgeDistance: rm % 2 === 0 ? px : py,
|
||||
}
|
||||
}
|
||||
|
||||
function getRectanglePath(shape: RectangleShape) {
|
||||
const { points, edgeDistance } = getRectangleDrawPoints(shape)
|
||||
const styles = getShapeStyle(shape.style)
|
||||
|
||||
const stroke = getStroke(points, {
|
||||
size: 1 + styles.strokeWidth * 2,
|
||||
thinning: 0.5,
|
||||
end: { taper: edgeDistance },
|
||||
start: { taper: edgeDistance },
|
||||
simulatePressure: false,
|
||||
last: true,
|
||||
})
|
||||
|
||||
return Utils.getSvgPathFromStroke(stroke)
|
||||
}
|
||||
|
||||
function getRectangleIndicatorPathData(shape: RectangleShape) {
|
||||
const { points } = getRectangleDrawPoints(shape)
|
||||
|
||||
return Utils.getSvgPathFromStroke(
|
||||
getStrokePoints(points).map((pt) => pt.point.slice(0, 2)),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
|
21
packages/vec/LICENSE
Normal file
21
packages/vec/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Stephen Ruiz Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
21
packages/www/LICENSE
Normal file
21
packages/www/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Stephen Ruiz Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -10906,10 +10906,10 @@ pbkdf2@^3.0.3:
|
|||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
perfect-freehand@^1.0.9:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-1.0.9.tgz#9b5fb96181004bcec62f3721d25cebea2ec34a3f"
|
||||
integrity sha512-YCbnozlv+Wqmxexi/3YDftCXNjqs6bpTZ01tN+pBJbiA3b8DTvgZGPgd8mxxaXkAQzEsgD0bkS1h9vy3EWnm2w==
|
||||
perfect-freehand@^1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-1.0.12.tgz#5f3614e099ad459ced4f50da9ce109fdf5b62b90"
|
||||
integrity sha512-tY6ZVUbF672WJdHQMSz+YT45WgkeoLtbD5oe0TQNAZWCd4xD8B0Pcv+3URMEqhErR4JenRgSlV4kT7zHsbvzVg==
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
|
|
Loading…
Reference in a new issue