kopia lustrzana https://github.com/Tldraw/Tldraw
135 wiersze
3.4 KiB
TypeScript
135 wiersze
3.4 KiB
TypeScript
import { pointInPolygon } from '@tldraw/primitives'
|
|
import { TLScribble, TLShapeId } from '@tldraw/tlschema'
|
|
import { ScribbleManager } from '../../../managers/ScribbleManager'
|
|
import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types'
|
|
import { StateNode } from '../../StateNode'
|
|
|
|
export class Erasing extends StateNode {
|
|
static override id = 'erasing'
|
|
|
|
private info = {} as TLPointerEventInfo
|
|
private scribble = {} as ScribbleManager
|
|
private markId = ''
|
|
private excludedShapeIds = new Set<TLShapeId>()
|
|
|
|
override onEnter = (info: TLPointerEventInfo) => {
|
|
this.markId = this.app.mark('erase scribble begin')
|
|
this.info = info
|
|
|
|
const { originPagePoint } = this.app.inputs
|
|
this.excludedShapeIds = new Set(
|
|
this.app.shapesArray
|
|
.filter(
|
|
(shape) =>
|
|
(shape.type === 'frame' || shape.type === 'group') &&
|
|
this.app.isPointInShape(originPagePoint, shape)
|
|
)
|
|
.map((shape) => shape.id)
|
|
)
|
|
|
|
this.startScribble()
|
|
this.update()
|
|
}
|
|
|
|
private startScribble = () => {
|
|
if (this.scribble.tick) {
|
|
this.app.off('tick', this.scribble?.tick)
|
|
}
|
|
|
|
this.scribble = new ScribbleManager({
|
|
onUpdate: this.onScribbleUpdate,
|
|
onComplete: this.onScribbleComplete,
|
|
color: 'muted-1',
|
|
size: 12,
|
|
})
|
|
|
|
this.app.on('tick', this.scribble.tick)
|
|
}
|
|
|
|
private pushPointToScribble = () => {
|
|
const { x, y } = this.app.inputs.currentPagePoint
|
|
this.scribble.addPoint(x, y)
|
|
}
|
|
|
|
private onScribbleUpdate = (scribble: TLScribble) => {
|
|
this.app.setScribble(scribble)
|
|
}
|
|
|
|
private onScribbleComplete = () => {
|
|
this.app.off('tick', this.scribble.tick)
|
|
this.app.setScribble(null)
|
|
}
|
|
|
|
override onExit = () => {
|
|
this.scribble.stop()
|
|
}
|
|
|
|
override onPointerMove = () => {
|
|
this.update()
|
|
}
|
|
|
|
override onPointerUp: TLEventHandlers['onPointerUp'] = () => {
|
|
this.complete()
|
|
}
|
|
|
|
override onCancel: TLEventHandlers['onCancel'] = () => {
|
|
this.cancel()
|
|
}
|
|
|
|
override onComplete: TLEventHandlers['onComplete'] = () => {
|
|
this.complete()
|
|
}
|
|
|
|
update() {
|
|
const {
|
|
shapesArray,
|
|
erasingIdsSet,
|
|
inputs: { currentPagePoint, previousPagePoint },
|
|
} = this.app
|
|
|
|
const { excludedShapeIds } = this
|
|
|
|
this.pushPointToScribble()
|
|
|
|
const erasing = new Set<TLShapeId>(erasingIdsSet)
|
|
|
|
for (const shape of shapesArray) {
|
|
// Skip groups
|
|
if (shape.type === 'group') continue
|
|
|
|
// Avoid testing masked shapes, unless the pointer is inside the mask
|
|
const pageMask = this.app.getPageMaskById(shape.id)
|
|
if (pageMask && !pointInPolygon(currentPagePoint, pageMask)) {
|
|
continue
|
|
}
|
|
|
|
// Hit test the shape using a line segment
|
|
const util = this.app.getShapeUtil(shape)
|
|
const A = this.app.getPointInShapeSpace(shape, previousPagePoint)
|
|
const B = this.app.getPointInShapeSpace(shape, currentPagePoint)
|
|
|
|
// If it's a hit, erase the outermost selectable shape
|
|
if (util.hitTestLineSegment(shape, A, B)) {
|
|
erasing.add(this.app.getOutermostSelectableShape(shape).id)
|
|
}
|
|
}
|
|
|
|
// Remove the hit shapes, except if they're in the list of excluded shapes
|
|
// (these excluded shapes will be any frames or groups the pointer was inside of
|
|
// when the user started erasing)
|
|
this.app.setErasingIds([...erasing].filter((id) => !excludedShapeIds.has(id)))
|
|
}
|
|
|
|
complete() {
|
|
this.app.deleteShapes(this.app.pageState.erasingIds)
|
|
this.app.setErasingIds([])
|
|
this.parent.transition('idle', {})
|
|
}
|
|
|
|
cancel() {
|
|
this.app.setErasingIds([])
|
|
this.app.bailToMark(this.markId)
|
|
this.parent.transition('idle', this.info)
|
|
}
|
|
}
|