kopia lustrzana https://github.com/Tldraw/Tldraw
137 wiersze
3.2 KiB
TypeScript
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))
|
|
}
|