Tldraw/packages/editor/src/lib/editor/shapeutils/GeoShapeUtil/components/DrawStyleEllipse.tsx

137 wiersze
3.2 KiB
TypeScript

import {
EASINGS,
getStrokeOutlinePoints,
getStrokePoints,
perimeterOfEllipse,
PI2,
setStrokePointRadii,
TAU,
Vec2d,
} from '@tldraw/primitives'
import { TLGeoShape, TLShapeId } from '@tldraw/tlschema'
import { rng } from '@tldraw/utils'
import * as React from 'react'
import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../../utils/svg'
import { getShapeFillSvg, getSvgWithShapeFill, ShapeFill } from '../../shared/ShapeFill'
import { TLExportColors } from '../../shared/TLExportColors'
export const DrawStyleEllipse = React.memo(function DrawStyleEllipse({
id,
w,
h,
strokeWidth: sw,
fill,
color,
}: Pick<TLGeoShape['props'], 'w' | 'h' | 'fill' | 'color'> & {
strokeWidth: number
id: TLShapeId
}) {
const innerPath = getEllipseIndicatorPath(id, w, h, sw)
const outerPath = getEllipsePath(id, w, h, sw)
return (
<>
<ShapeFill d={innerPath} color={color} fill={fill} />
<path d={outerPath} fill="currentColor" strokeWidth={0} pointerEvents="all" />
</>
)
})
export function DrawStyleEllipseSvg({
id,
w,
h,
strokeWidth: sw,
fill,
color,
colors,
}: Pick<TLGeoShape['props'], 'w' | 'h' | 'fill' | 'color'> & {
strokeWidth: number
id: TLShapeId
colors: TLExportColors
}) {
const strokeElement = document.createElementNS('http://www.w3.org/2000/svg', 'path')
strokeElement.setAttribute('d', getEllipsePath(id, w, h, sw))
strokeElement.setAttribute('fill', colors.fill[color])
// Get the fill element, if any
const fillElement = getShapeFillSvg({
d: getEllipseIndicatorPath(id, w, h, sw),
fill,
color,
colors,
})
return getSvgWithShapeFill(strokeElement, fillElement)
}
export function getEllipseStrokeOptions(strokeWidth: number) {
return {
size: 1 + strokeWidth,
thinning: 0.25,
end: { taper: strokeWidth },
start: { taper: strokeWidth },
streamline: 0,
smoothing: 1,
simulatePressure: false,
}
}
export function getEllipseStrokePoints(
id: string,
width: number,
height: number,
strokeWidth: number
) {
const getRandom = rng(id)
const rx = width / 2
const ry = height / 2
const perimeter = perimeterOfEllipse(rx, ry)
const points: Vec2d[] = []
const start = PI2 * getRandom()
const length = PI2 + TAU / 2 + Math.abs(getRandom()) * TAU
const count = Math.max(16, perimeter / 10)
for (let i = 0; i < count; i++) {
const t = i / (count - 1)
const r = start + t * length
const c = Math.cos(r)
const s = Math.sin(r)
points.push(
new Vec2d(
rx * c + width * 0.5 + 0.05 * getRandom(),
ry * s + height / 2 + 0.05 * getRandom(),
Math.min(
1,
0.5 +
Math.abs(0.5 - (getRandom() > 0 ? EASINGS.easeInOutSine(t) : EASINGS.easeInExpo(t))) / 2
)
)
)
}
return getStrokePoints(points, getEllipseStrokeOptions(strokeWidth))
}
export function getEllipsePath(id: string, width: number, height: number, strokeWidth: number) {
const options = getEllipseStrokeOptions(strokeWidth)
return getSvgPathFromStroke(
getStrokeOutlinePoints(
setStrokePointRadii(getEllipseStrokePoints(id, width, height, strokeWidth), options),
options
)
)
}
export function getEllipseIndicatorPath(
id: string,
width: number,
height: number,
strokeWidth: number
) {
return getSvgPathFromStrokePoints(getEllipseStrokePoints(id, width, height, strokeWidth))
}