UNPKG

tldraw

Version:

A tiny little drawing editor.

186 lines (152 loc) 4.72 kB
import { Group2d, StateNode, TLArrowShape, TLPointerEventInfo, TLShapeId, Vec, } from '@tldraw/editor' import { ArrowShapeUtil } from '../../../shapes/arrow/ArrowShapeUtil' import { getArrowBodyGeometry, getArrowLabelDefaultPosition, } from '../../../shapes/arrow/arrowLabel' import { startEditingShapeWithRichText } from '../selectHelpers' export class PointingArrowLabel extends StateNode { static override id = 'pointing_arrow_label' shapeId = '' as TLShapeId markId = '' wasAlreadySelected = false didDrag = false didCtrlOnEnter = false private info = {} as TLPointerEventInfo & { shape: TLArrowShape onInteractionEnd?: string | (() => void) isCreating: boolean } private updateCursor() { this.editor.setCursor({ type: 'grabbing', rotation: 0 }) } override onEnter( info: TLPointerEventInfo & { shape: TLArrowShape onInteractionEnd?: string | (() => void) isCreating: boolean } ) { const { shape } = info if (typeof info.onInteractionEnd === 'string') { this.parent.setCurrentToolIdMask(info.onInteractionEnd) } this.info = info this.shapeId = shape.id this.didDrag = false this.didCtrlOnEnter = info.accelKey this.wasAlreadySelected = this.editor.getOnlySelectedShapeId() === shape.id this.updateCursor() const geometry = this.editor.getShapeGeometry<Group2d>(shape) const labelGeometry = geometry.children[1] if (!labelGeometry) { throw Error(`Expected to find an arrow label geometry for shape: ${shape.id}`) } const currentPagePoint = this.editor.inputs.getCurrentPagePoint() const pointInShapeSpace = this.editor.getPointInShapeSpace(shape, currentPagePoint) this._labelDragOffset = Vec.Sub(labelGeometry.center, pointInShapeSpace) this.markId = this.editor.markHistoryStoppingPoint('label-drag start') const additiveSelectionKey = info.shiftKey || info.accelKey if (additiveSelectionKey) { const selectedShapeIds = this.editor.getSelectedShapeIds() this.editor.setSelectedShapes([...selectedShapeIds, this.shapeId]) return } this.editor.setSelectedShapes([this.shapeId]) } override onExit() { this.parent.setCurrentToolIdMask(undefined) this.editor.setCursor({ type: 'default', rotation: 0 }) } private _labelDragOffset = new Vec(0, 0) override onPointerMove() { const isDragging = this.editor.inputs.getIsDragging() if (!isDragging) return if (this.didCtrlOnEnter) { this.parent.transition('brushing', this.info) return } const shape = this.editor.getShape<TLArrowShape>(this.shapeId) if (!shape) return const options = this.editor.getShapeUtil<ArrowShapeUtil>('arrow').options const geometry = getArrowBodyGeometry(this.editor, shape) const transform = this.editor.getShapePageTransform(shape.id) const pointInShapeSpace = this.editor .getPointInShapeSpace(shape, this.editor.inputs.getCurrentPagePoint()) .add(this._labelDragOffset) const defaultLabelPosition = getArrowLabelDefaultPosition(this.editor, shape) let nextLabelPosition = geometry.uninterpolateAlongEdge(pointInShapeSpace) if (isNaN(nextLabelPosition)) { nextLabelPosition = defaultLabelPosition } const nextLabelPoint = transform.applyToPoint(geometry.interpolateAlongEdge(nextLabelPosition)) const labelDefaultPoint = transform.applyToPoint( geometry.interpolateAlongEdge(defaultLabelPosition) ) if ( Vec.DistMin( nextLabelPoint, labelDefaultPoint, options.labelCenterSnapDistance / this.editor.getZoomLevel() ) ) { nextLabelPosition = defaultLabelPosition } this.didDrag = true this.editor.updateShape({ id: shape.id, type: shape.type, props: { labelPosition: nextLabelPosition }, }) } override onPointerUp() { const shape = this.editor.getShape<TLArrowShape>(this.shapeId) if (!shape) return if (this.didDrag || !this.wasAlreadySelected) { this.complete() } else if (this.editor.canEditShape(shape)) { startEditingShapeWithRichText(this.editor, shape.id) } } override onCancel() { this.cancel() } override onComplete() { this.cancel() } override onInterrupt() { this.cancel() } private complete() { const { onInteractionEnd } = this.info if (onInteractionEnd) { if (typeof onInteractionEnd === 'string') { this.editor.setCurrentTool(onInteractionEnd, {}) } else { onInteractionEnd() } return } this.parent.transition('idle') } private cancel() { this.editor.bailToMark(this.markId) const { onInteractionEnd } = this.info if (onInteractionEnd) { if (typeof onInteractionEnd === 'string') { this.editor.setCurrentTool(onInteractionEnd, {}) } else { onInteractionEnd() } return } this.parent.transition('idle') } }