Fix arrow bend behavior
This commit is contained in:
parent
cefd294c13
commit
62a3da0498
5 changed files with 63 additions and 125 deletions
|
@ -58,7 +58,6 @@ export function Page<T extends TLShape>({
|
|||
<Bounds zoom={zoom} bounds={bounds} isLocked={isLocked} rotation={rotation} />
|
||||
)}
|
||||
{!hideIndicators &&
|
||||
selectedIds.length > 1 &&
|
||||
selectedIds
|
||||
.filter(Boolean)
|
||||
.map((id) => (
|
||||
|
|
|
@ -96,6 +96,8 @@ export function useShapeTree<T extends TLShape, M extends Record<string, unknown
|
|||
// Call onChange callback when number of rendering shapes changes
|
||||
|
||||
if (shapesToRender.length !== rPreviousCount.current) {
|
||||
// Use a timeout to clear call stack, in case the onChange handleer
|
||||
// produces a new state change (React won't like that)
|
||||
setTimeout(() => onChange?.(shapesToRender.map((shape) => shape.id)), 0)
|
||||
rPreviousCount.current = shapesToRender.length
|
||||
}
|
||||
|
|
|
@ -510,7 +510,7 @@ export class Vec {
|
|||
return Array.from(Array(steps))
|
||||
.map((_, i) => {
|
||||
const t = i / steps
|
||||
return t * t * t
|
||||
return t * t * t * t
|
||||
})
|
||||
.map((t) => Vec.round([...Vec.lrp(a, b, t), (1 - t) / 2]))
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
|
||||
const arrowHeadLength = Math.min(arrowDist / 3, strokeWidth * 8)
|
||||
|
||||
let shaftPath: JSX.Element
|
||||
let shaftPath: JSX.Element | null
|
||||
let startArrowHead: { left: number[]; right: number[] } | undefined
|
||||
let endArrowHead: { left: number[]; right: number[] } | undefined
|
||||
|
||||
|
@ -120,31 +120,32 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
}
|
||||
|
||||
// Straight arrow path
|
||||
shaftPath = (
|
||||
<>
|
||||
<path
|
||||
d={path}
|
||||
fill="none"
|
||||
strokeWidth={Math.max(8, strokeWidth * 2)}
|
||||
strokeDasharray="none"
|
||||
strokeDashoffset="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
pointerEvents="stroke"
|
||||
/>
|
||||
<path
|
||||
d={path}
|
||||
fill={styles.stroke}
|
||||
stroke={styles.stroke}
|
||||
strokeWidth={sw}
|
||||
strokeDasharray={strokeDasharray}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
pointerEvents="stroke"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
shaftPath =
|
||||
arrowDist > 2 ? (
|
||||
<>
|
||||
<path
|
||||
d={path}
|
||||
fill="none"
|
||||
strokeWidth={Math.max(8, strokeWidth * 2)}
|
||||
strokeDasharray="none"
|
||||
strokeDashoffset="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
pointerEvents="stroke"
|
||||
/>
|
||||
<path
|
||||
d={path}
|
||||
fill={styles.stroke}
|
||||
stroke={styles.stroke}
|
||||
strokeWidth={sw}
|
||||
strokeDasharray={strokeDasharray}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
pointerEvents="stroke"
|
||||
/>
|
||||
</>
|
||||
) : null
|
||||
} else {
|
||||
const circle = getCtp(shape)
|
||||
|
||||
|
@ -496,6 +497,18 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
}
|
||||
})
|
||||
|
||||
nextHandles = {
|
||||
...nextHandles,
|
||||
start: {
|
||||
...nextHandles.start,
|
||||
point: Vec.round(nextHandles.start.point),
|
||||
},
|
||||
end: {
|
||||
...nextHandles.end,
|
||||
point: Vec.round(nextHandles.end.point),
|
||||
},
|
||||
}
|
||||
|
||||
// If the user is moving the bend handle, we want to move the bend point
|
||||
if ('bend' in handles) {
|
||||
const { start, end, bend } = nextHandles
|
||||
|
@ -524,23 +537,16 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
// negative, otherwise it's positive.
|
||||
const angleToBend = Vec.angle(start.point, bendPoint)
|
||||
|
||||
if (Utils.isAngleBetween(angle, angle + Math.PI, angleToBend)) {
|
||||
// If resulting bend is low enough that the handle will snap to center,
|
||||
// then also snap the bend to center
|
||||
if (Vec.isEqual(midPoint, getBendPoint(nextHandles, nextBend))) {
|
||||
nextBend = 0
|
||||
} else if (Utils.isAngleBetween(angle, angle + Math.PI, angleToBend)) {
|
||||
// Otherwise, fix the bend direction
|
||||
nextBend *= -1
|
||||
}
|
||||
}
|
||||
|
||||
nextHandles = {
|
||||
...nextHandles,
|
||||
start: {
|
||||
...nextHandles.start,
|
||||
point: Vec.round(nextHandles.start.point),
|
||||
},
|
||||
end: {
|
||||
...nextHandles.end,
|
||||
point: Vec.round(nextHandles.end.point),
|
||||
},
|
||||
}
|
||||
|
||||
const nextShape = {
|
||||
point: shape.point,
|
||||
bend: nextBend,
|
||||
|
@ -613,16 +619,14 @@ function renderFreehandArrowShaft(shape: ArrowShape) {
|
|||
|
||||
const strokeWidth = +getShapeStyle(style).strokeWidth * 2
|
||||
|
||||
const st = Math.abs(getRandom())
|
||||
|
||||
const stroke = getStroke(
|
||||
[...Vec.pointsBetween(start.point, end.point), end.point, end.point, end.point, end.point],
|
||||
[...Vec.pointsBetween(start.point, end.point), end.point, end.point, end.point],
|
||||
{
|
||||
size: strokeWidth / 2,
|
||||
thinning: 0.5 + getRandom() * 0.3,
|
||||
easing: (t) => t * t,
|
||||
end: { taper: 1 },
|
||||
start: { taper: 1 + 32 * (st * st * st) },
|
||||
end: { cap: true },
|
||||
start: { cap: true },
|
||||
simulatePressure: true,
|
||||
last: true,
|
||||
}
|
||||
|
@ -641,8 +645,6 @@ function renderCurvedFreehandArrowShaft(shape: ArrowShape, circle: number[]) {
|
|||
|
||||
const strokeWidth = +getShapeStyle(style).strokeWidth * 2
|
||||
|
||||
const st = Math.abs(getRandom())
|
||||
|
||||
const center = [circle[0], circle[1]]
|
||||
const radius = circle[2]
|
||||
|
||||
|
@ -658,16 +660,12 @@ function renderCurvedFreehandArrowShaft(shape: ArrowShape, circle: number[]) {
|
|||
points.push(Vec.round(Vec.nudgeAtAngle(center, angle, radius)))
|
||||
}
|
||||
|
||||
const stroke = getStroke([...points, end.point, end.point], {
|
||||
const stroke = getStroke([...points, end.point, end.point, end.point], {
|
||||
size: strokeWidth / 2,
|
||||
thinning: 0.5 + getRandom() * 0.3,
|
||||
easing: (t) => t * t,
|
||||
end: {
|
||||
taper: shape.decorations?.end ? 1 : 1 + strokeWidth * 5 * (st * st * st),
|
||||
},
|
||||
start: {
|
||||
taper: shape.decorations?.start ? 1 : 1 + strokeWidth * 5 * (st * st * st),
|
||||
},
|
||||
end: { cap: true },
|
||||
start: { cap: true },
|
||||
simulatePressure: true,
|
||||
streamline: 0.01,
|
||||
last: true,
|
||||
|
@ -782,68 +780,3 @@ function getArrowPath(shape: ArrowShape) {
|
|||
|
||||
return path.join(' ')
|
||||
}
|
||||
|
||||
// function getArrowHeadPath(shape: ArrowShape, point: number[], inset: number[]) {
|
||||
// const { left, right } = getArrowHeadPoints(shape, point, inset)
|
||||
// return ['M', left, 'L', point, right].join(' ')
|
||||
// }
|
||||
|
||||
// function getArrowHeadPoints(shape: ArrowShape, point: number[], inset: number[]) {
|
||||
// // Use the shape's random seed to create minor offsets for the angles
|
||||
// const getRandom = Utils.rng(shape.id)
|
||||
|
||||
// return {
|
||||
// left: Vec.rotWith(inset, point, Math.PI / 6 + (Math.PI / 12) * getRandom()),
|
||||
// right: Vec.rotWith(inset, point, -Math.PI / 6 + (Math.PI / 12) * getRandom()),
|
||||
// }
|
||||
// }
|
||||
|
||||
// function getDrawArrowPath(shape: ArrowShape) {
|
||||
// const {
|
||||
// decorations,
|
||||
// handles: { start, end, bend: _bend },
|
||||
// style,
|
||||
// } = shape
|
||||
|
||||
// const { strokeWidth } = getShapeStyle(style, false)
|
||||
|
||||
// const arrowDist = Vec.dist(start.point, end.point)
|
||||
|
||||
// const arrowHeadLength = Math.min(arrowDist / 3, strokeWidth * 8)
|
||||
|
||||
// const path: (string | number)[] = []
|
||||
|
||||
// const isStraightLine = Vec.dist(_bend.point, Vec.round(Vec.med(start.point, end.point))) < 1
|
||||
|
||||
// if (isStraightLine) {
|
||||
// // Path (line segment)
|
||||
// path.push(`M ${start.point} L ${end.point}`)
|
||||
|
||||
// // Start arrow head
|
||||
// if (decorations?.start) {
|
||||
// path.push(getStraightArrowHeadPath(start.point, end.point, arrowHeadLength))
|
||||
// }
|
||||
|
||||
// // End arrow head
|
||||
// if (decorations?.end) {
|
||||
// path.push(getStraightArrowHeadPath(end.point, start.point, arrowHeadLength))
|
||||
// }
|
||||
// } else {
|
||||
// const { center, radius, length } = getArrowArc(shape)
|
||||
|
||||
// // Path (arc)
|
||||
// path.push(`M ${start.point} A ${radius} ${radius} 0 0 ${length > 0 ? '1' : '0'} ${end.point}`)
|
||||
|
||||
// // Start Arrow head
|
||||
// if (decorations?.start) {
|
||||
// path.push(getCurvedArrowHeadPath(start.point, arrowHeadLength, center, radius, length < 0))
|
||||
// }
|
||||
|
||||
// // End arrow head
|
||||
// if (decorations?.end) {
|
||||
// path.push(getCurvedArrowHeadPath(end.point, arrowHeadLength, center, radius, length >= 0))
|
||||
// }
|
||||
// }
|
||||
|
||||
// return path.join(' ')
|
||||
// }
|
||||
|
|
|
@ -11,6 +11,9 @@ import {
|
|||
TLDrawRenderInfo,
|
||||
} from '~types'
|
||||
|
||||
// TODO
|
||||
// [ ] - Make sure that fill does not extend drawn shape at corners
|
||||
|
||||
export class Rectangle extends TLDrawShapeUtil<RectangleShape> {
|
||||
type = TLDrawShapeType.Rectangle as const
|
||||
toolType = TLDrawToolType.Bounds
|
||||
|
@ -351,13 +354,14 @@ function renderPath(shape: RectangleShape) {
|
|||
Math.floor(5 + getRandom() * 4)
|
||||
)
|
||||
|
||||
const stroke = getStroke([...lines.flat().slice(2), ...lines[0], ...lines[0].slice(4)], {
|
||||
size: 1 + +styles.strokeWidth,
|
||||
thinning: 0.6,
|
||||
const stroke = getStroke([...lines.flat().slice(4), ...lines[0], ...lines[0].slice(4)], {
|
||||
size: 1 + styles.strokeWidth,
|
||||
thinning: 0.618,
|
||||
easing: (t) => t * t * t * t,
|
||||
end: { taper: +styles.strokeWidth * 20 },
|
||||
start: { taper: +styles.strokeWidth * 20 },
|
||||
end: { cap: true },
|
||||
start: { cap: true },
|
||||
simulatePressure: false,
|
||||
last: true,
|
||||
})
|
||||
|
||||
return Utils.getSvgPathFromStroke(stroke)
|
||||
|
|
Loading…
Reference in a new issue