Adds support for solid / dotted / dashed draw lines

This commit is contained in:
Steve Ruiz 2021-07-03 14:28:43 +01:00
parent 3b553e32fe
commit 8a747fe82f
5 changed files with 127 additions and 38 deletions

File diff suppressed because one or more lines are too long

View file

@ -182,7 +182,7 @@ class Inputs {
return ( return (
Date.now() - this.pointerUpTime < DOUBLE_CLICK_DURATION && Date.now() - this.pointerUpTime < DOUBLE_CLICK_DURATION &&
vec.dist(origin, point) < 8 vec.dist(origin, point) < 4
) )
} }

View file

@ -15,6 +15,7 @@ import { registerShapeUtils } from './register'
const rotatedCache = new WeakMap<DrawShape, number[][]>([]) const rotatedCache = new WeakMap<DrawShape, number[][]>([])
const pathCache = new WeakMap<DrawShape['points'], string>([]) const pathCache = new WeakMap<DrawShape['points'], string>([])
const simplePathCache = new WeakMap<DrawShape['points'], string>([])
const polygonCache = new WeakMap<DrawShape['points'], string>([]) const polygonCache = new WeakMap<DrawShape['points'], string>([])
const draw = registerShapeUtils<DrawShape>({ const draw = registerShapeUtils<DrawShape>({
@ -47,6 +48,8 @@ const draw = registerShapeUtils<DrawShape>({
const styles = getShapeStyle(style) const styles = getShapeStyle(style)
const strokeWidth = +styles.strokeWidth
if (!pathCache.has(points)) { if (!pathCache.has(points)) {
renderPath(shape, style) renderPath(shape, style)
} }
@ -54,7 +57,7 @@ const draw = registerShapeUtils<DrawShape>({
if (points.length > 0 && points.length < 3) { if (points.length > 0 && points.length < 3) {
return ( return (
<g id={id}> <g id={id}>
<circle r={+styles.strokeWidth * 0.618} fill={styles.stroke} /> <circle r={strokeWidth * 0.618} fill={styles.stroke} />
</g> </g>
) )
} }
@ -63,21 +66,63 @@ const draw = registerShapeUtils<DrawShape>({
points.length > 3 && points.length > 3 &&
vec.dist(points[0], points[points.length - 1]) < +styles.strokeWidth * 2 vec.dist(points[0], points[points.length - 1]) < +styles.strokeWidth * 2
if (shouldFill && !polygonCache.has(points)) { if (shape.style.dash === DashStyle.Draw) {
renderFill(shape, style) if (shouldFill && !polygonCache.has(points)) {
renderFill(shape, style)
}
return (
<g id={id}>
{shouldFill && (
<path
d={polygonCache.get(points)}
fill={styles.fill}
strokeWidth="0"
stroke="none"
/>
)}
<path d={pathCache.get(points)} fill={styles.stroke} />
</g>
)
} }
// For solid, dash and dotted lines, draw a regular stroke path
const strokeDasharray = {
[DashStyle.Dotted]: `${strokeWidth / 10} ${strokeWidth * 3}`,
[DashStyle.Dashed]: `${strokeWidth * 3} ${strokeWidth * 3}`,
[DashStyle.Solid]: `none`,
}[style.dash]
const strokeDashoffset = {
[DashStyle.Dotted]: `-${strokeWidth / 20}`,
[DashStyle.Dashed]: `-${strokeWidth}`,
[DashStyle.Solid]: `none`,
}[style.dash]
if (!simplePathCache.has(points)) {
simplePathCache.set(points, getSolidStrokePath(points))
}
const path = simplePathCache.get(points)
return ( return (
<g id={id}> <g id={id}>
{shouldFill && ( {style.dash !== DashStyle.Solid && (
<path <path
d={polygonCache.get(points)} d={path}
fill={styles.fill} strokeWidth={strokeWidth * 2}
strokeWidth="0" fill="transparent"
stroke="none" stroke="transparent"
/> />
)} )}
<path d={pathCache.get(points)} fill={styles.stroke} /> <path
d={path}
strokeWidth={strokeWidth * 1.618}
fill={shouldFill ? styles.fill : 'none'}
strokeDasharray={strokeDasharray}
strokeDashoffset={strokeDashoffset}
/>
</g> </g>
) )
}, },
@ -168,12 +213,12 @@ const draw = registerShapeUtils<DrawShape>({
return this return this
}, },
applyStyles(shape, style) { // applyStyles(shape, style) {
const styles = { ...shape.style, ...style } // const styles = { ...shape.style, ...style }
styles.dash = DashStyle.Solid // styles.dash = DashStyle.Solid
shape.style = styles // shape.style = styles
return this // return this
}, // },
onSessionComplete(shape) { onSessionComplete(shape) {
const bounds = this.getBounds(shape) const bounds = this.getBounds(shape)
@ -243,3 +288,43 @@ function renderFill(shape: DrawShape, style: ShapeStyles) {
) )
) )
} }
function getSolidStrokePath(stroke: number[][]) {
let len = stroke.length
if (len === 0) return 'M 0 0 L 0 0'
if (len < 3) return `M ${stroke[0][0]} ${stroke[0][1]}`
// Remove duplicates from points
stroke = stroke.reduce<number[][]>((acc, pt, i) => {
if (i === 0 || !vec.isEqual(pt, acc[i - 1])) {
acc.push(pt)
}
return acc
}, [])
len = stroke.length
const d = stroke.reduce(
(acc, [x0, y0], i, arr) => {
if (i === stroke.length - 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', stroke[0][0], stroke[0][1], 'Q']
)
const path = d.join(' ').replaceAll(/(\s[0-9]*\.[0-9]{2})([0-9]*)\b/g, '$1')
return path
}

View file

@ -53,7 +53,7 @@ const initialData: Data = {
currentStyle: { currentStyle: {
size: SizeStyle.Medium, size: SizeStyle.Medium,
color: ColorStyle.Black, color: ColorStyle.Black,
dash: DashStyle.Solid, dash: DashStyle.Draw,
isFilled: false, isFilled: false,
}, },
activeTool: 'select', activeTool: 'select',

View file

@ -351,7 +351,7 @@ export default class Vec {
} }
static round = (a: number[], d = 5): number[] => { static round = (a: number[], d = 5): number[] => {
return a.map((v) => Number(v.toPrecision(d))) return a.map((v) => +v.toPrecision(d))
} }
/** /**