expand arrow bounds based on interpolated points (#90)

This commit is contained in:
Steve Ruiz 2021-09-13 22:32:17 +01:00 committed by GitHub
parent cc6c486918
commit fc9b9fa3e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 72 additions and 46 deletions

View file

@ -15,7 +15,7 @@ export const Container = React.memo(
const rBounds = usePosition(bounds, rotation)
return (
<div id={id} ref={rBounds} className={className + ' tl-positioned'}>
<div id={id} ref={rBounds} className={['tl-positioned', className || ''].join(' ')}>
{children}
</div>
)

View file

@ -12,26 +12,22 @@ interface HandleProps {
export const Handle = React.memo(({ id, point }: HandleProps) => {
const events = useHandleEvents(id)
const bounds = React.useMemo(
() =>
Utils.translateBounds(
return (
<Container
bounds={Utils.translateBounds(
{
minX: 0,
minY: 0,
maxX: 32,
maxY: 32,
width: 32,
height: 32,
maxX: 0,
maxY: 0,
width: 0,
height: 0,
},
point
),
[point]
)
return (
<Container bounds={bounds}>
)}
>
<SVGContainer>
<g className="tl-handles" {...events}>
<g className="tl-handle" {...events}>
<circle className="tl-handle-bg" pointerEvents="all" />
<circle className="tl-counter-scaled tl-handle" pointerEvents="none" r={4} />
</g>

View file

@ -25,12 +25,7 @@ export const Shape = <T extends TLShape, E extends Element, M>({
const events = useShapeEvents(shape.id, isCurrentParent)
return (
<Container
id={shape.id}
className={'tl-shape' + (isCurrentParent ? 'tl-current-parent' : '')}
bounds={bounds}
rotation={shape.rotation}
>
<Container id={shape.id} className="tl-shape" bounds={bounds} rotation={shape.rotation}>
<RenderedShape
shape={shape}
isBinding={isBinding}

View file

@ -113,7 +113,7 @@ const tlcss = css`
--tl-scale: calc(1 / var(--tl-zoom));
--tl-camera-x: 0px;
--tl-camera-y: 0px;
--tl-padding: calc(64px * var(--tl-scale));
--tl-padding: calc(64px * max(1, var(--tl-scale)));
position: relative;
top: 0px;
left: 0px;
@ -279,23 +279,23 @@ const tlcss = css`
stroke-width: 2px;
}
.tl-handles {
.tl-handle {
pointer-events: all;
}
.tl-handles:hover > .tl-handle-bg {
.tl-handle:hover .tl-handle-bg {
fill: var(--tl-selectFill);
}
.tl-handles:hover > .tl-handle-bg > * {
.tl-handle:hover .tl-handle-bg > * {
stroke: var(--tl-selectFill);
}
.tl-handles:active > .tl-handle-bg {
.tl-handle:active .tl-handle-bg {
fill: var(--tl-selectFill);
}
.tl-handles:active > .tl-handle-bg > * {
.tl-handle:active .tl-handle-bg > * {
stroke: var(--tl-selectFill);
}
@ -309,7 +309,7 @@ const tlcss = css`
fill: transparent;
stroke: none;
pointer-events: all;
r: calc(20 / max(1, var(--tl-zoom)));
r: calc(20px / max(1, var(--tl-zoom)));
}
.tl-binding-indicator {

View file

@ -206,8 +206,13 @@ export const ShapeUtil = function <T extends TLShape, E extends Element, M = any
Object.assign(this, fn.call(this))
Object.assign(this, fn.call(this))
this.getBounds = this.getBounds.bind(this)
this.Component = this.Component.bind(this)
// Make sure all functions are bound to this
for (const entry of Object.entries(this)) {
if (entry[1] instanceof Function) {
this[entry[0] as keyof typeof this] = this[entry[0]].bind(this)
}
}
this._Component = React.forwardRef(this.Component)
return this

View file

@ -6,7 +6,6 @@
"outDir": "./dist/types",
"rootDir": "src",
"baseUrl": "src",
"emitDeclarationOnly": false,
"paths": {
"+*": ["./*"],
"@tldraw/vec": ["../vec"],

View file

@ -6,7 +6,6 @@
"outDir": "./dist/types",
"rootDir": "src",
"baseUrl": "src",
"emitDeclarationOnly": false,
"paths": {
"@tldraw/vec": ["../vec"]
}

View file

@ -213,6 +213,8 @@ export const Arrow = new ShapeUtil<ArrowShape, SVGSVGElement, TLDrawMeta>(() =>
const sw = strokeWidth * 1.618
const dots = getArcPoints(shape)
return (
<SVGContainer ref={ref} {...events}>
<g pointerEvents="none">
@ -249,7 +251,7 @@ export const Arrow = new ShapeUtil<ArrowShape, SVGSVGElement, TLDrawMeta>(() =>
},
Indicator({ shape }) {
const path = Utils.getFromCache(simplePathCache, shape.handles, () => getArrowPath(shape))
const path = getArrowPath(shape)
return <path d={path} />
},
@ -260,20 +262,25 @@ export const Arrow = new ShapeUtil<ArrowShape, SVGSVGElement, TLDrawMeta>(() =>
getBounds(shape) {
const bounds = Utils.getFromCache(this.boundsCache, shape, () => {
const { start, bend, end } = shape.handles
return Utils.getBoundsFromPoints([start.point, bend.point, end.point])
const points = getArcPoints(shape)
return Utils.getBoundsFromPoints(points)
})
return Utils.translateBounds(bounds, shape.point)
},
getRotatedBounds(shape) {
const { start, bend, end } = shape.handles
let points = getArcPoints(shape)
return Utils.translateBounds(
Utils.getBoundsFromPoints([start.point, bend.point, end.point], shape.rotation),
shape.point
)
const { minX, minY, maxX, maxY } = Utils.getBoundsFromPoints(points)
if (shape.rotation !== 0) {
points = points.map((pt) =>
Vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], shape.rotation || 0)
)
}
return Utils.translateBounds(Utils.getBoundsFromPoints(points), shape.point)
},
getCenter(shape) {
@ -544,11 +551,10 @@ export const Arrow = new ShapeUtil<ArrowShape, SVGSVGElement, TLDrawMeta>(() =>
// Zero out the handles to prevent handles with negative points. If a handle's x or y
// is below zero, we need to move the shape left or up to make it zero.
const bounds = Utils.getBoundsFromPoints(
Object.values(nextShape.handles).map((handle) => handle.point)
)
const topLeft = shape.point
const nextBounds = this.getBounds({ ...nextShape } as ArrowShape)
const offset = [bounds.minX, bounds.minY]
const offset = Vec.sub([nextBounds.minX, nextBounds.minY], topLeft)
if (!Vec.isEqual(offset, [0, 0])) {
Object.values(nextShape.handles).forEach((handle) => {
@ -766,3 +772,22 @@ function getArrowPath(shape: ArrowShape) {
return path.join(' ')
}
function getArcPoints(shape: ArrowShape) {
const { start, bend, end } = shape.handles
const points: number[][] = [start.point, end.point]
if (Vec.dist2(bend.point, Vec.med(start.point, end.point)) > 4) {
// We're an arc, calculate points along the arc
const { center, radius } = getArrowArc(shape)
const startAngle = Vec.angle(center, start.point)
const endAngle = Vec.angle(center, end.point)
for (let i = 1 / 20; i < 1; i += 1 / 20) {
const angle = Utils.lerpAngles(startAngle, endAngle, i)
points.push(Vec.nudgeAtAngle(center, angle, radius))
}
}
return points
}

View file

@ -10,6 +10,7 @@ import {
import { Vec } from '@tldraw/vec'
import { Utils } from '@tldraw/core'
import { TLDR } from '~state/tldr'
import { ThickArrowDownIcon } from '@radix-ui/react-icons'
export class ArrowSession implements Session {
id = 'transform_single'
@ -18,6 +19,7 @@ export class ArrowSession implements Session {
delta = [0, 0]
offset = [0, 0]
origin: number[]
topLeft: number[]
initialShape: ArrowShape
handleId: 'start' | 'end'
bindableShapeIds: string[]
@ -33,6 +35,7 @@ export class ArrowSession implements Session {
this.origin = point
this.handleId = handleId
this.initialShape = TLDR.getShape<ArrowShape>(data, shapeId, data.appState.currentPageId)
this.topLeft = this.initialShape.point
this.bindableShapeIds = TLDR.getBindableShapeIds(data)
const initialBindingId = this.initialShape.handles[this.handleId].bindingId
@ -66,7 +69,9 @@ export class ArrowSession implements Session {
}
// First update the handle's next point
const change = TLDR.getShapeUtils<ArrowShape>(shape.type).onHandleChange(
const utils = TLDR.getShapeUtils<ArrowShape>(shape.type)
const change = utils.onHandleChange(
shape,
{
[handleId]: handle,

View file

@ -9,6 +9,7 @@ export class HandleSession implements Session {
status = TLDrawStatus.TranslatingHandle
commandId: string
delta = [0, 0]
topLeft: number[]
origin: number[]
shiftKey = false
initialShape: ShapesWithProp<'handles'>
@ -17,6 +18,7 @@ export class HandleSession implements Session {
constructor(data: Data, handleId: string, point: number[], commandId = 'move_handle') {
const { currentPageId } = data.appState
const shapeId = TLDR.getSelectedIds(data, currentPageId)[0]
this.topLeft = point
this.origin = point
this.handleId = handleId
this.initialShape = TLDR.getShape(data, shapeId, currentPageId)
@ -43,6 +45,7 @@ export class HandleSession implements Session {
}
// First update the handle's next point
const change = TLDR.getShapeUtils(shape).onHandleChange(
shape,
{

View file

@ -6,7 +6,6 @@
"outDir": "./dist/types",
"rootDir": "src",
"baseUrl": "src",
"emitDeclarationOnly": false,
"paths": {
"~*": ["./*"],
"@tldraw/core": ["../core"],