expand arrow bounds based on interpolated points (#90)
This commit is contained in:
parent
cc6c486918
commit
fc9b9fa3e3
11 changed files with 72 additions and 46 deletions
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"outDir": "./dist/types",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "src",
|
||||
"emitDeclarationOnly": false,
|
||||
"paths": {
|
||||
"+*": ["./*"],
|
||||
"@tldraw/vec": ["../vec"],
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"outDir": "./dist/types",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "src",
|
||||
"emitDeclarationOnly": false,
|
||||
"paths": {
|
||||
"@tldraw/vec": ["../vec"]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"outDir": "./dist/types",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "src",
|
||||
"emitDeclarationOnly": false,
|
||||
"paths": {
|
||||
"~*": ["./*"],
|
||||
"@tldraw/core": ["../core"],
|
||||
|
|
Loading…
Reference in a new issue