Adds support for solid / dotted / dashed draw lines
This commit is contained in:
parent
3b553e32fe
commit
8a747fe82f
5 changed files with 127 additions and 38 deletions
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue