arrows: update cursor only when in Select mode (#2742)
The cursor was updating for the arrow label even when in other tools (e.g. Draw) and it should only be updating when in Select mode. That addresses this issue: https://github.com/tldraw/tldraw/issues/2750 Also, there was a problem with arrows and zero length arrows and labels. The problem was actually in `Vec.ts` where we were dividing by zero. Addresses this bug https://github.com/tldraw/tldraw/issues/2749 ### Change Type - [x] `patch` — Bug fix ### Test Plan 1. Make sure that the cursor is only applicable to the Select tool. ### Release Notes - Cursor tweak for arrow labels.
This commit is contained in:
parent
533d389953
commit
8cd8117acf
7 changed files with 48 additions and 27 deletions
|
@ -1143,10 +1143,6 @@ input,
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-arrow-label__inner p {
|
|
||||||
cursor: var(--tl-cursor-grab);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tl-arrow-label p,
|
.tl-arrow-label p,
|
||||||
.tl-arrow-label textarea {
|
.tl-arrow-label textarea {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
|
|
@ -37,7 +37,9 @@ export function getCurvedArrowInfo(
|
||||||
const terminalsInArrowSpace = getArrowTerminalsInArrowSpace(editor, shape)
|
const terminalsInArrowSpace = getArrowTerminalsInArrowSpace(editor, shape)
|
||||||
|
|
||||||
const med = Vec.Med(terminalsInArrowSpace.start, terminalsInArrowSpace.end) // point between start and end
|
const med = Vec.Med(terminalsInArrowSpace.start, terminalsInArrowSpace.end) // point between start and end
|
||||||
const u = Vec.Sub(terminalsInArrowSpace.end, terminalsInArrowSpace.start).uni() // unit vector between start and end
|
const distance = Vec.Sub(terminalsInArrowSpace.end, terminalsInArrowSpace.start)
|
||||||
|
// Check for divide-by-zero before we call uni()
|
||||||
|
const u = Vec.Len(distance) ? distance.uni() : Vec.From(distance) // unit vector between start and end
|
||||||
const middle = Vec.Add(med, u.per().mul(-bend)) // middle handle
|
const middle = Vec.Add(med, u.per().mul(-bend)) // middle handle
|
||||||
|
|
||||||
const startShapeInfo = getBoundShapeInfoForTerminal(editor, shape.props.start)
|
const startShapeInfo = getBoundShapeInfoForTerminal(editor, shape.props.start)
|
||||||
|
|
|
@ -109,7 +109,9 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): TLArr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const u = Vec.Sub(b, a).uni()
|
const distance = Vec.Sub(b, a)
|
||||||
|
// Check for divide-by-zero before we call uni()
|
||||||
|
const u = Vec.Len(distance) ? distance.uni() : Vec.From(distance)
|
||||||
const didFlip = !Vec.Equals(u, uAB)
|
const didFlip = !Vec.Equals(u, uAB)
|
||||||
|
|
||||||
// If the arrow is bound non-exact to a start shape and the
|
// If the arrow is bound non-exact to a start shape and the
|
||||||
|
|
|
@ -143,6 +143,10 @@ describe('Vec.Uni', () => {
|
||||||
expect(Vec.Uni(new Vec(0, 10))).toMatchObject(new Vec(0, 1))
|
expect(Vec.Uni(new Vec(0, 10))).toMatchObject(new Vec(0, 1))
|
||||||
expect(Vec.Uni(new Vec(10, 10))).toMatchObject(new Vec(0.7071067811865475, 0.7071067811865475))
|
expect(Vec.Uni(new Vec(10, 10))).toMatchObject(new Vec(0.7071067811865475, 0.7071067811865475))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Divide-by-zero spits out NaN (at the moment)', () => {
|
||||||
|
expect(Vec.Uni(new Vec(0, 0))).toMatchObject(new Vec(NaN, NaN))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Vec.Tan', () => {
|
describe('Vec.Tan', () => {
|
||||||
|
|
|
@ -33,6 +33,10 @@ function getArrowPoints(
|
||||||
: ints[0]
|
: ints[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isNaN(P0.x) || isNaN(P0.y)) {
|
||||||
|
P0 = info.start.point
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
point: PT,
|
point: PT,
|
||||||
int: P0,
|
int: P0,
|
||||||
|
|
|
@ -34,6 +34,13 @@ export class Idle extends StateNode {
|
||||||
|
|
||||||
override onPointerMove: TLEventHandlers['onPointerMove'] = () => {
|
override onPointerMove: TLEventHandlers['onPointerMove'] = () => {
|
||||||
updateHoveredId(this.editor)
|
updateHoveredId(this.editor)
|
||||||
|
|
||||||
|
const hitShape = this.editor.getHoveredShape()
|
||||||
|
if (this.isOverArrowLabelTest(hitShape)) {
|
||||||
|
this.editor.setCursor({ type: 'pointer', rotation: 0 })
|
||||||
|
} else {
|
||||||
|
this.editor.setCursor({ type: 'default', rotation: 0 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => {
|
override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => {
|
||||||
|
@ -93,22 +100,11 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
case 'shape': {
|
case 'shape': {
|
||||||
const { shape } = info
|
const { shape } = info
|
||||||
const pointInShapeSpace = this.editor.getPointInShapeSpace(
|
if (this.isOverArrowLabelTest(shape)) {
|
||||||
shape,
|
|
||||||
this.editor.inputs.currentPagePoint
|
|
||||||
)
|
|
||||||
// todo: Extract into general hit test for arrows
|
|
||||||
if (this.editor.isShapeOfType<TLArrowShape>(shape, 'arrow')) {
|
|
||||||
// How should we handle multiple labels? Do shapes ever have multiple labels?
|
|
||||||
const labelGeometry = this.editor.getShapeGeometry<Group2d>(shape).children[1]
|
|
||||||
// Knowing what we know about arrows... if the shape has no text in its label,
|
|
||||||
// then the label geometry should not be there.
|
|
||||||
if (labelGeometry && pointInPolygon(pointInShapeSpace, labelGeometry.vertices)) {
|
|
||||||
// We're moving the label on a shape.
|
// We're moving the label on a shape.
|
||||||
this.parent.transition('pointing_arrow_label', info)
|
this.parent.transition('pointing_arrow_label', info)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.editor.isShapeOrAncestorLocked(shape)) {
|
if (this.editor.isShapeOrAncestorLocked(shape)) {
|
||||||
this.parent.transition('pointing_canvas', info)
|
this.parent.transition('pointing_canvas', info)
|
||||||
|
@ -499,6 +495,28 @@ export class Idle extends StateNode {
|
||||||
|
|
||||||
isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
|
isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
|
||||||
|
|
||||||
|
isOverArrowLabelTest(shape: TLShape | undefined) {
|
||||||
|
if (!shape) return false
|
||||||
|
|
||||||
|
const pointInShapeSpace = this.editor.getPointInShapeSpace(
|
||||||
|
shape,
|
||||||
|
this.editor.inputs.currentPagePoint
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo: Extract into general hit test for arrows
|
||||||
|
if (this.editor.isShapeOfType<TLArrowShape>(shape, 'arrow')) {
|
||||||
|
// How should we handle multiple labels? Do shapes ever have multiple labels?
|
||||||
|
const labelGeometry = this.editor.getShapeGeometry<Group2d>(shape).children[1]
|
||||||
|
// Knowing what we know about arrows... if the shape has no text in its label,
|
||||||
|
// then the label geometry should not be there.
|
||||||
|
if (labelGeometry && pointInPolygon(pointInShapeSpace, labelGeometry.vertices)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
handleDoubleClickOnCanvas(info: TLClickEventInfo) {
|
handleDoubleClickOnCanvas(info: TLClickEventInfo) {
|
||||||
// Create text shape and transition to editing_shape
|
// Create text shape and transition to editing_shape
|
||||||
if (this.editor.getInstanceState().isReadonly) return
|
if (this.editor.getInstanceState().isReadonly) return
|
||||||
|
|
|
@ -24,12 +24,7 @@ export class PointingArrowLabel extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateCursor() {
|
private updateCursor() {
|
||||||
this.editor.updateInstanceState({
|
this.editor.setCursor({ type: 'grabbing', rotation: 0 })
|
||||||
cursor: {
|
|
||||||
type: 'grabbing',
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override onEnter = (
|
override onEnter = (
|
||||||
|
|
Loading…
Reference in a new issue