kopia lustrzana https://github.com/Tldraw/Tldraw
721 wiersze
19 KiB
TypeScript
721 wiersze
19 KiB
TypeScript
import {
|
|
DRAG_DISTANCE,
|
|
Mat,
|
|
StateNode,
|
|
TLDefaultSizeStyle,
|
|
TLDrawShape,
|
|
TLDrawShapeSegment,
|
|
TLEventHandlers,
|
|
TLHighlightShape,
|
|
TLPointerEventInfo,
|
|
TLShapePartial,
|
|
Vec,
|
|
VecModel,
|
|
createShapeId,
|
|
last,
|
|
snapAngle,
|
|
structuredClone,
|
|
toFixed,
|
|
uniqueId,
|
|
} from '@tldraw/editor'
|
|
import { STROKE_SIZES } from '../../shared/default-shape-constants'
|
|
|
|
type DrawableShape = TLDrawShape | TLHighlightShape
|
|
|
|
export class Drawing extends StateNode {
|
|
static override id = 'drawing'
|
|
|
|
info = {} as TLPointerEventInfo
|
|
|
|
initialShape?: DrawableShape
|
|
|
|
override shapeType = this.parent.id === 'highlight' ? ('highlight' as const) : ('draw' as const)
|
|
|
|
util = this.editor.getShapeUtil(this.shapeType)
|
|
|
|
isPen = false
|
|
|
|
segmentMode = 'free' as 'free' | 'straight' | 'starting_straight' | 'starting_free'
|
|
|
|
didJustShiftClickToExtendPreviousShapeLine = false
|
|
|
|
pagePointWhereCurrentSegmentChanged = {} as Vec
|
|
|
|
pagePointWhereNextSegmentChanged = null as Vec | null
|
|
|
|
lastRecordedPoint = {} as Vec
|
|
mergeNextPoint = false
|
|
currentLineLength = 0
|
|
|
|
canDraw = false
|
|
|
|
markId = null as null | string
|
|
|
|
override onEnter = (info: TLPointerEventInfo) => {
|
|
this.markId = null
|
|
this.info = info
|
|
this.canDraw = !this.editor.getIsMenuOpen()
|
|
this.lastRecordedPoint = this.editor.inputs.currentPagePoint.clone()
|
|
if (this.canDraw) {
|
|
this.startShape()
|
|
}
|
|
}
|
|
|
|
override onPointerMove: TLEventHandlers['onPointerMove'] = () => {
|
|
const { inputs } = this.editor
|
|
|
|
if (this.isPen !== inputs.isPen) {
|
|
// The user made a palm gesture before starting a pen gesture;
|
|
// ideally we'd start the new shape here but we could also just bail
|
|
// as the next interaction will work correctly
|
|
if (this.markId) {
|
|
this.editor.bailToMark(this.markId)
|
|
this.startShape()
|
|
return
|
|
}
|
|
} else {
|
|
// If we came in from a menu but have no started dragging...
|
|
if (!this.canDraw && inputs.isDragging) {
|
|
this.startShape()
|
|
this.canDraw = true // bad name
|
|
}
|
|
}
|
|
|
|
if (this.canDraw) {
|
|
if (inputs.isPen) {
|
|
// Don't update the shape if we haven't moved far enough from the last time we recorded a point
|
|
if (
|
|
Vec.Dist(inputs.currentPagePoint, this.lastRecordedPoint) >=
|
|
1 / this.editor.getZoomLevel()
|
|
) {
|
|
this.lastRecordedPoint = inputs.currentPagePoint.clone()
|
|
this.mergeNextPoint = false
|
|
} else {
|
|
this.mergeNextPoint = true
|
|
}
|
|
} else {
|
|
this.mergeNextPoint = false
|
|
}
|
|
|
|
this.updateDrawingShape()
|
|
}
|
|
}
|
|
|
|
override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
|
if (info.key === 'Shift') {
|
|
switch (this.segmentMode) {
|
|
case 'free': {
|
|
// We've just entered straight mode, go to straight mode
|
|
this.segmentMode = 'starting_straight'
|
|
this.pagePointWhereNextSegmentChanged = this.editor.inputs.currentPagePoint.clone()
|
|
break
|
|
}
|
|
case 'starting_free': {
|
|
this.segmentMode = 'starting_straight'
|
|
}
|
|
}
|
|
}
|
|
this.updateDrawingShape()
|
|
}
|
|
|
|
override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => {
|
|
if (info.key === 'Shift') {
|
|
this.editor.snaps.clearIndicators()
|
|
|
|
switch (this.segmentMode) {
|
|
case 'straight': {
|
|
// We've just exited straight mode, go back to free mode
|
|
this.segmentMode = 'starting_free'
|
|
this.pagePointWhereNextSegmentChanged = this.editor.inputs.currentPagePoint.clone()
|
|
break
|
|
}
|
|
case 'starting_straight': {
|
|
this.pagePointWhereNextSegmentChanged = null
|
|
this.segmentMode = 'free'
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
this.updateDrawingShape()
|
|
}
|
|
|
|
override onExit? = () => {
|
|
this.editor.snaps.clearIndicators()
|
|
this.pagePointWhereCurrentSegmentChanged = this.editor.inputs.currentPagePoint.clone()
|
|
}
|
|
|
|
canClose() {
|
|
return this.shapeType !== 'highlight'
|
|
}
|
|
|
|
getIsClosed(segments: TLDrawShapeSegment[], size: TLDefaultSizeStyle) {
|
|
if (!this.canClose()) return false
|
|
|
|
const strokeWidth = STROKE_SIZES[size]
|
|
const firstPoint = segments[0].points[0]
|
|
const lastSegment = segments[segments.length - 1]
|
|
const lastPoint = lastSegment.points[lastSegment.points.length - 1]
|
|
|
|
return (
|
|
firstPoint !== lastPoint &&
|
|
this.currentLineLength > strokeWidth * 4 &&
|
|
Vec.DistMin(firstPoint, lastPoint, strokeWidth * 2)
|
|
)
|
|
}
|
|
|
|
private startShape() {
|
|
const {
|
|
inputs: { originPagePoint, isPen },
|
|
} = this.editor
|
|
|
|
this.markId = 'draw start ' + uniqueId()
|
|
this.editor.mark(this.markId)
|
|
|
|
this.isPen = isPen
|
|
|
|
const pressure = this.isPen ? this.info.point.z! * 1.25 : 0.5
|
|
|
|
this.segmentMode = this.editor.inputs.shiftKey ? 'straight' : 'free'
|
|
|
|
this.didJustShiftClickToExtendPreviousShapeLine = false
|
|
|
|
this.lastRecordedPoint = originPagePoint.clone()
|
|
|
|
if (this.initialShape) {
|
|
const shape = this.editor.getShape<DrawableShape>(this.initialShape.id)
|
|
|
|
if (shape && this.segmentMode === 'straight') {
|
|
// Connect dots
|
|
|
|
this.didJustShiftClickToExtendPreviousShapeLine = true
|
|
|
|
const prevSegment = last(shape.props.segments)
|
|
if (!prevSegment) throw Error('Expected a previous segment!')
|
|
const prevPoint = last(prevSegment.points)
|
|
if (!prevPoint) throw Error('Expected a previous point!')
|
|
|
|
const { x, y } = this.editor.getPointInShapeSpace(shape, originPagePoint).toFixed()
|
|
|
|
const pressure = this.isPen ? this.info.point.z! * 1.25 : 0.5
|
|
|
|
const newSegment: TLDrawShapeSegment = {
|
|
type: this.segmentMode,
|
|
points: [
|
|
{
|
|
x: prevPoint.x,
|
|
y: prevPoint.y,
|
|
z: +pressure.toFixed(2),
|
|
},
|
|
{
|
|
x,
|
|
y,
|
|
z: +pressure.toFixed(2),
|
|
},
|
|
],
|
|
}
|
|
|
|
// Convert prevPoint to page space
|
|
const prevPointPageSpace = Mat.applyToPoint(
|
|
this.editor.getShapePageTransform(shape.id)!,
|
|
prevPoint
|
|
)
|
|
this.pagePointWhereCurrentSegmentChanged = prevPointPageSpace
|
|
this.pagePointWhereNextSegmentChanged = null
|
|
const segments = [...shape.props.segments, newSegment]
|
|
|
|
if (this.currentLineLength < STROKE_SIZES[shape.props.size] * 4) {
|
|
this.currentLineLength = this.getLineLength(segments)
|
|
}
|
|
|
|
const shapePartial: TLShapePartial<DrawableShape> = {
|
|
id: shape.id,
|
|
type: this.shapeType,
|
|
props: {
|
|
segments,
|
|
},
|
|
}
|
|
|
|
if (this.canClose()) {
|
|
;(shapePartial as TLShapePartial<TLDrawShape>).props!.isClosed = this.getIsClosed(
|
|
segments,
|
|
shape.props.size
|
|
)
|
|
}
|
|
|
|
this.editor.updateShapes<TLDrawShape | TLHighlightShape>([shapePartial])
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
// Create a new shape
|
|
|
|
this.pagePointWhereCurrentSegmentChanged = originPagePoint.clone()
|
|
const id = createShapeId()
|
|
|
|
this.editor.createShapes<DrawableShape>([
|
|
{
|
|
id,
|
|
type: this.shapeType,
|
|
x: originPagePoint.x,
|
|
y: originPagePoint.y,
|
|
props: {
|
|
isPen: this.isPen,
|
|
segments: [
|
|
{
|
|
type: this.segmentMode,
|
|
points: [
|
|
{
|
|
x: 0,
|
|
y: 0,
|
|
z: +pressure.toFixed(2),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
])
|
|
this.currentLineLength = 0
|
|
this.initialShape = this.editor.getShape<DrawableShape>(id)
|
|
}
|
|
|
|
private updateDrawingShape() {
|
|
const { initialShape } = this
|
|
const { inputs } = this.editor
|
|
|
|
if (!initialShape) return
|
|
|
|
const {
|
|
id,
|
|
props: { size },
|
|
} = initialShape
|
|
|
|
const shape = this.editor.getShape<DrawableShape>(id)!
|
|
|
|
if (!shape) return
|
|
|
|
const { segments } = shape.props
|
|
|
|
const { x, y, z } = this.editor.getPointInShapeSpace(shape, inputs.currentPagePoint).toFixed()
|
|
|
|
const newPoint = { x, y, z: this.isPen ? +(z! * 1.25).toFixed(2) : 0.5 }
|
|
|
|
switch (this.segmentMode) {
|
|
case 'starting_straight': {
|
|
const { pagePointWhereNextSegmentChanged } = this
|
|
|
|
if (pagePointWhereNextSegmentChanged === null) {
|
|
throw Error('We should have a point where the segment changed')
|
|
}
|
|
|
|
const hasMovedFarEnough =
|
|
Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
|
|
|
|
// Find the distance from where the pointer was when shift was released and
|
|
// where it is now; if it's far enough away, then update the page point where
|
|
// the current segment changed (to match the pagepoint where next segment changed)
|
|
// and set the pagepoint where next segment changed to null.
|
|
if (hasMovedFarEnough) {
|
|
this.pagePointWhereCurrentSegmentChanged = this.pagePointWhereNextSegmentChanged!.clone()
|
|
this.pagePointWhereNextSegmentChanged = null
|
|
|
|
// Set the new mode
|
|
this.segmentMode = 'straight'
|
|
|
|
const prevSegment = last(segments)
|
|
if (!prevSegment) throw Error('Expected a previous segment!')
|
|
|
|
const prevLastPoint = last(prevSegment.points)
|
|
if (!prevLastPoint) throw Error('Expected a previous last point!')
|
|
|
|
let newSegment: TLDrawShapeSegment
|
|
|
|
const newLastPoint = this.editor
|
|
.getPointInShapeSpace(shape, this.pagePointWhereCurrentSegmentChanged)
|
|
.toFixed()
|
|
.toJson()
|
|
|
|
if (prevSegment.type === 'straight') {
|
|
this.currentLineLength += Vec.Dist(prevLastPoint, newLastPoint)
|
|
|
|
newSegment = {
|
|
type: 'straight',
|
|
points: [{ ...prevLastPoint }, newLastPoint],
|
|
}
|
|
|
|
const transform = this.editor.getShapePageTransform(shape)!
|
|
|
|
this.pagePointWhereCurrentSegmentChanged = Mat.applyToPoint(transform, prevLastPoint)
|
|
} else {
|
|
newSegment = {
|
|
type: 'straight',
|
|
points: [newLastPoint, newPoint],
|
|
}
|
|
}
|
|
|
|
const shapePartial: TLShapePartial<DrawableShape> = {
|
|
id,
|
|
type: this.shapeType,
|
|
props: {
|
|
segments: [...segments, newSegment],
|
|
},
|
|
}
|
|
|
|
if (this.canClose()) {
|
|
;(shapePartial as TLShapePartial<TLDrawShape>).props!.isClosed = this.getIsClosed(
|
|
segments,
|
|
size
|
|
)
|
|
}
|
|
|
|
this.editor.updateShapes<TLDrawShape | TLHighlightShape>([shapePartial], {
|
|
squashing: true,
|
|
})
|
|
}
|
|
break
|
|
}
|
|
case 'starting_free': {
|
|
const { pagePointWhereNextSegmentChanged } = this
|
|
|
|
if (pagePointWhereNextSegmentChanged === null) {
|
|
throw Error('We should have a point where the segment changed')
|
|
}
|
|
|
|
const hasMovedFarEnough =
|
|
Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
|
|
|
|
// Find the distance from where the pointer was when shift was released and
|
|
// where it is now; if it's far enough away, then update the page point where
|
|
// the current segment changed (to match the pagepoint where next segment changed)
|
|
// and set the pagepoint where next segment changed to null.
|
|
if (hasMovedFarEnough) {
|
|
this.pagePointWhereCurrentSegmentChanged = this.pagePointWhereNextSegmentChanged!.clone()
|
|
this.pagePointWhereNextSegmentChanged = null
|
|
|
|
// Set the new mode
|
|
this.segmentMode = 'free'
|
|
|
|
const newSegments = segments.slice()
|
|
const prevStraightSegment = newSegments[newSegments.length - 1]
|
|
const prevPoint = last(prevStraightSegment.points)
|
|
|
|
if (!prevPoint) {
|
|
throw Error('No previous point!')
|
|
}
|
|
|
|
// Create the new free segment and interpolate the points between where the last line
|
|
// ended and where the pointer is now
|
|
const newFreeSegment: TLDrawShapeSegment = {
|
|
type: 'free',
|
|
points: [...Vec.PointsBetween(prevPoint, newPoint, 6).map((p) => p.toFixed().toJson())],
|
|
}
|
|
|
|
const finalSegments = [...newSegments, newFreeSegment]
|
|
|
|
if (this.currentLineLength < STROKE_SIZES[shape.props.size] * 4) {
|
|
this.currentLineLength = this.getLineLength(finalSegments)
|
|
}
|
|
|
|
const shapePartial: TLShapePartial<DrawableShape> = {
|
|
id,
|
|
type: this.shapeType,
|
|
props: {
|
|
segments: finalSegments,
|
|
},
|
|
}
|
|
|
|
if (this.canClose()) {
|
|
;(shapePartial as TLShapePartial<TLDrawShape>).props!.isClosed = this.getIsClosed(
|
|
finalSegments,
|
|
size
|
|
)
|
|
}
|
|
|
|
this.editor.updateShapes([shapePartial], { squashing: true })
|
|
}
|
|
|
|
break
|
|
}
|
|
case 'straight': {
|
|
const newSegments = segments.slice()
|
|
const newSegment = newSegments[newSegments.length - 1]
|
|
|
|
const { pagePointWhereCurrentSegmentChanged } = this
|
|
const { ctrlKey, currentPagePoint } = this.editor.inputs
|
|
|
|
if (!pagePointWhereCurrentSegmentChanged)
|
|
throw Error('We should have a point where the segment changed')
|
|
|
|
let pagePoint: VecModel
|
|
let shouldSnapToAngle = false
|
|
|
|
if (this.didJustShiftClickToExtendPreviousShapeLine) {
|
|
if (this.editor.inputs.isDragging) {
|
|
// If we've just shift clicked to extend a line, only snap once we've started dragging
|
|
shouldSnapToAngle = !ctrlKey
|
|
this.didJustShiftClickToExtendPreviousShapeLine = false
|
|
} else {
|
|
// noop
|
|
}
|
|
} else {
|
|
// If we're not shift clicking to extend a line, but we're holding shift, then we should snap
|
|
shouldSnapToAngle = !ctrlKey // don't snap angle while snapping line
|
|
}
|
|
|
|
let newPoint = this.editor.getPointInShapeSpace(shape, currentPagePoint).toFixed().toJson()
|
|
let didSnap = false
|
|
let snapSegment: TLDrawShapeSegment | undefined = undefined
|
|
|
|
const shouldSnap = this.editor.user.getIsSnapMode() ? !ctrlKey : ctrlKey
|
|
|
|
if (shouldSnap) {
|
|
if (newSegments.length > 2) {
|
|
let nearestPoint: VecModel | undefined = undefined
|
|
let minDistance = 8 / this.editor.getZoomLevel()
|
|
|
|
// Don't try to snap to the last two segments
|
|
for (let i = 0, n = segments.length - 2; i < n; i++) {
|
|
const segment = segments[i]
|
|
if (!segment) break
|
|
if (segment.type === 'free') continue
|
|
|
|
const first = segment.points[0]
|
|
const lastPoint = last(segment.points)
|
|
if (!(first && lastPoint)) continue
|
|
|
|
// Snap to the nearest point on the segment, if it's closer than the previous snapped point
|
|
const nearestPointOnSegment = Vec.NearestPointOnLineSegment(
|
|
first,
|
|
lastPoint,
|
|
newPoint
|
|
)
|
|
|
|
if (Vec.DistMin(nearestPointOnSegment, newPoint, minDistance)) {
|
|
nearestPoint = nearestPointOnSegment.toFixed().toJson()
|
|
minDistance = Vec.Dist(nearestPointOnSegment, newPoint)
|
|
snapSegment = segment
|
|
break
|
|
}
|
|
}
|
|
|
|
if (nearestPoint) {
|
|
didSnap = true
|
|
newPoint = nearestPoint
|
|
}
|
|
}
|
|
}
|
|
|
|
if (didSnap && snapSegment) {
|
|
const transform = this.editor.getShapePageTransform(shape)!
|
|
const first = snapSegment.points[0]
|
|
const lastPoint = last(snapSegment.points)
|
|
if (!lastPoint) throw Error('Expected a last point!')
|
|
|
|
const A = Mat.applyToPoint(transform, first)
|
|
|
|
const B = Mat.applyToPoint(transform, lastPoint)
|
|
|
|
const snappedPoint = Mat.applyToPoint(transform, newPoint)
|
|
|
|
this.editor.snaps.setIndicators([
|
|
{
|
|
id: uniqueId(),
|
|
type: 'points',
|
|
points: [A, snappedPoint, B],
|
|
},
|
|
])
|
|
} else {
|
|
this.editor.snaps.clearIndicators()
|
|
|
|
if (shouldSnapToAngle) {
|
|
// Snap line angle to nearest 15 degrees
|
|
const currentAngle = Vec.Angle(pagePointWhereCurrentSegmentChanged, currentPagePoint)
|
|
const snappedAngle = snapAngle(currentAngle, 24)
|
|
const angleDiff = snappedAngle - currentAngle
|
|
|
|
pagePoint = Vec.RotWith(
|
|
currentPagePoint,
|
|
pagePointWhereCurrentSegmentChanged,
|
|
angleDiff
|
|
)
|
|
} else {
|
|
pagePoint = currentPagePoint
|
|
}
|
|
|
|
newPoint = this.editor.getPointInShapeSpace(shape, pagePoint).toFixed().toJson()
|
|
}
|
|
|
|
// If the previous segment is a one point free shape and is the first segment of the line,
|
|
// then the user just did a click-and-immediately-press-shift to create a new straight line
|
|
// without continuing the previous line. In this case, we want to remove the previous segment.
|
|
|
|
this.currentLineLength += Vec.Dist(newSegment.points[0], newPoint)
|
|
|
|
newSegments[newSegments.length - 1] = {
|
|
...newSegment,
|
|
type: 'straight',
|
|
points: [newSegment.points[0], newPoint],
|
|
}
|
|
|
|
const shapePartial: TLShapePartial<DrawableShape> = {
|
|
id,
|
|
type: this.shapeType,
|
|
props: {
|
|
segments: newSegments,
|
|
},
|
|
}
|
|
|
|
if (this.canClose()) {
|
|
;(shapePartial as TLShapePartial<TLDrawShape>).props!.isClosed = this.getIsClosed(
|
|
segments,
|
|
size
|
|
)
|
|
}
|
|
|
|
this.editor.updateShapes([shapePartial], { squashing: true })
|
|
|
|
break
|
|
}
|
|
case 'free': {
|
|
const newSegments = segments.slice()
|
|
const newSegment = newSegments[newSegments.length - 1]
|
|
const newPoints = [...newSegment.points]
|
|
|
|
if (newPoints.length && this.mergeNextPoint) {
|
|
const { z } = newPoints[newPoints.length - 1]
|
|
newPoints[newPoints.length - 1] = {
|
|
x: newPoint.x,
|
|
y: newPoint.y,
|
|
z: z ? Math.max(z, newPoint.z) : newPoint.z,
|
|
}
|
|
// Note: we could recompute the line length here, but it's not really necessary
|
|
// this.currentLineLength = this.getLineLength(newSegments)
|
|
} else {
|
|
this.currentLineLength += Vec.Dist(newPoints[newPoints.length - 1], newPoint)
|
|
newPoints.push(newPoint)
|
|
}
|
|
|
|
newSegments[newSegments.length - 1] = {
|
|
...newSegment,
|
|
points: newPoints,
|
|
}
|
|
|
|
if (this.currentLineLength < STROKE_SIZES[shape.props.size] * 4) {
|
|
this.currentLineLength = this.getLineLength(newSegments)
|
|
}
|
|
|
|
const shapePartial: TLShapePartial<DrawableShape> = {
|
|
id,
|
|
type: this.shapeType,
|
|
props: {
|
|
segments: newSegments,
|
|
},
|
|
}
|
|
|
|
if (this.canClose()) {
|
|
;(shapePartial as TLShapePartial<TLDrawShape>).props!.isClosed = this.getIsClosed(
|
|
newSegments,
|
|
size
|
|
)
|
|
}
|
|
|
|
this.editor.updateShapes([shapePartial], { squashing: true })
|
|
|
|
// Set a maximum length for the lines array; after 200 points, complete the line.
|
|
if (newPoints.length > 500) {
|
|
this.editor.updateShapes([{ id, type: this.shapeType, props: { isComplete: true } }])
|
|
|
|
const newShapeId = createShapeId()
|
|
|
|
this.editor.createShapes<DrawableShape>([
|
|
{
|
|
id: newShapeId,
|
|
type: this.shapeType,
|
|
x: toFixed(inputs.currentPagePoint.x),
|
|
y: toFixed(inputs.currentPagePoint.y),
|
|
props: {
|
|
isPen: this.isPen,
|
|
segments: [
|
|
{
|
|
type: 'free',
|
|
points: [{ x: 0, y: 0, z: this.isPen ? +(z! * 1.25).toFixed() : 0.5 }],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
])
|
|
|
|
this.initialShape = structuredClone(this.editor.getShape<DrawableShape>(newShapeId)!)
|
|
this.mergeNextPoint = false
|
|
this.lastRecordedPoint = inputs.currentPagePoint.clone()
|
|
this.currentLineLength = 0
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
private getLineLength(segments: TLDrawShapeSegment[]) {
|
|
let length = 0
|
|
|
|
for (const segment of segments) {
|
|
for (let i = 0; i < segment.points.length - 1; i++) {
|
|
const A = segment.points[i]
|
|
const B = segment.points[i + 1]
|
|
length += Vec.Dist2(B, A)
|
|
}
|
|
}
|
|
|
|
return Math.sqrt(length)
|
|
}
|
|
|
|
override onPointerUp: TLEventHandlers['onPointerUp'] = () => {
|
|
this.complete()
|
|
}
|
|
|
|
override onCancel: TLEventHandlers['onCancel'] = () => {
|
|
this.cancel()
|
|
}
|
|
|
|
override onComplete: TLEventHandlers['onComplete'] = () => {
|
|
this.complete()
|
|
}
|
|
|
|
override onInterrupt: TLEventHandlers['onInterrupt'] = () => {
|
|
if (this.editor.inputs.isDragging) {
|
|
return
|
|
}
|
|
|
|
if (this.markId) {
|
|
this.editor.bailToMark(this.markId)
|
|
}
|
|
this.cancel()
|
|
}
|
|
|
|
complete() {
|
|
// If we weren't focused when the drawing shape started, and if
|
|
// we haven't dragged far enough to start dragging, then don't do
|
|
// anything here. Most likely we clicked back into the canvas from
|
|
// a menu or other UI element.
|
|
if (!this.canDraw) {
|
|
this.cancel()
|
|
return
|
|
}
|
|
|
|
const { initialShape } = this
|
|
if (!initialShape) return
|
|
this.editor.updateShapes([
|
|
{ id: initialShape.id, type: initialShape.type, props: { isComplete: true } },
|
|
])
|
|
|
|
this.parent.transition('idle')
|
|
}
|
|
|
|
cancel() {
|
|
this.parent.transition('idle', this.info)
|
|
}
|
|
}
|