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

View file

@ -2,6 +2,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import * as RadioGroup from '@radix-ui/react-radio-group' import * as RadioGroup from '@radix-ui/react-radio-group'
import * as Panel from './panel' import * as Panel from './panel'
import styled from 'styles' import styled from 'styles'
import { forwardRef } from 'react'
export const breakpoints: any = { '@initial': 'mobile', '@sm': 'small' } export const breakpoints: any = { '@initial': 'mobile', '@sm': 'small' }
@ -367,9 +368,11 @@ export function BoxIcon({
) )
} }
export function IsFilledFillIcon(): JSX.Element { export const IsFilledFillIcon = forwardRef<SVGSVGElement, { color?: string }>(
(props, ref) => {
return ( return (
<svg <svg
ref={ref}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -386,7 +389,8 @@ export function IsFilledFillIcon(): JSX.Element {
/> />
</svg> </svg>
) )
} }
)
export const ButtonsRow = styled('div', { export const ButtonsRow = styled('div', {
position: 'relative', position: 'relative',

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,6 +66,7 @@ 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 (shape.style.dash === DashStyle.Draw) {
if (shouldFill && !polygonCache.has(points)) { if (shouldFill && !polygonCache.has(points)) {
renderFill(shape, style) renderFill(shape, style)
} }
@ -80,6 +84,47 @@ const draw = registerShapeUtils<DrawShape>({
<path d={pathCache.get(points)} fill={styles.stroke} /> <path d={pathCache.get(points)} fill={styles.stroke} />
</g> </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 (
<g id={id}>
{style.dash !== DashStyle.Solid && (
<path
d={path}
strokeWidth={strokeWidth * 2}
fill="transparent"
stroke="transparent"
/>
)}
<path
d={path}
strokeWidth={strokeWidth * 1.618}
fill={shouldFill ? styles.fill : 'none'}
strokeDasharray={strokeDasharray}
strokeDashoffset={strokeDashoffset}
/>
</g>
)
}, },
getBounds(shape) { getBounds(shape) {
@ -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))
} }
/** /**