[fix] restore bg option, fix calculations (#1765)
This PR fixes a bug introduced with #1751 where pointing the bounds of rotated selections would not correctly hit the bounds background. ### Change Type - [x] `patch` — Bug fix ### Test Plan 1. Create a rotated selection. 2. Point into the bounds background - [x] Unit Tests
This commit is contained in:
parent
56cf77a8cd
commit
28b92c5e76
7 changed files with 114 additions and 22 deletions
|
@ -1736,6 +1736,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
||||||
getOutlineSegments(shape: Shape): Vec2d[][];
|
getOutlineSegments(shape: Shape): Vec2d[][];
|
||||||
hideResizeHandles: TLShapeUtilFlag<Shape>;
|
hideResizeHandles: TLShapeUtilFlag<Shape>;
|
||||||
hideRotateHandle: TLShapeUtilFlag<Shape>;
|
hideRotateHandle: TLShapeUtilFlag<Shape>;
|
||||||
|
hideSelectionBoundsBg: TLShapeUtilFlag<Shape>;
|
||||||
hideSelectionBoundsFg: TLShapeUtilFlag<Shape>;
|
hideSelectionBoundsFg: TLShapeUtilFlag<Shape>;
|
||||||
abstract indicator(shape: Shape): any;
|
abstract indicator(shape: Shape): any;
|
||||||
isAspectRatioLocked: TLShapeUtilFlag<Shape>;
|
isAspectRatioLocked: TLShapeUtilFlag<Shape>;
|
||||||
|
|
|
@ -149,6 +149,13 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
||||||
*/
|
*/
|
||||||
hideRotateHandle: TLShapeUtilFlag<Shape> = () => false
|
hideRotateHandle: TLShapeUtilFlag<Shape> = () => false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the shape should hide its selection bounds background when selected.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
hideSelectionBoundsBg: TLShapeUtilFlag<Shape> = () => false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the shape should hide its selection bounds foreground when selected.
|
* Whether the shape should hide its selection bounds foreground when selected.
|
||||||
*
|
*
|
||||||
|
|
|
@ -99,14 +99,11 @@ export const TldrawSelectionForeground: TLSelectionForegroundComponent = track(
|
||||||
onlyShape &&
|
onlyShape &&
|
||||||
editor.isShapeOfType<TLTextShape>(onlyShape, 'text'))
|
editor.isShapeOfType<TLTextShape>(onlyShape, 'text'))
|
||||||
|
|
||||||
if (
|
if (onlyShape && shouldDisplayBox) {
|
||||||
onlyShape &&
|
if (IS_FIREFOX && editor.isShapeOfType<TLEmbedShape>(onlyShape, 'embed')) {
|
||||||
editor.isShapeOfType<TLEmbedShape>(onlyShape, 'embed') &&
|
|
||||||
shouldDisplayBox &&
|
|
||||||
IS_FIREFOX
|
|
||||||
) {
|
|
||||||
shouldDisplayBox = false
|
shouldDisplayBox = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const showCropHandles =
|
const showCropHandles =
|
||||||
editor.isInAny(
|
editor.isInAny(
|
||||||
|
|
|
@ -64,6 +64,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
||||||
override canSnap = () => true
|
override canSnap = () => true
|
||||||
override hideResizeHandles: TLShapeUtilFlag<TLArrowShape> = () => true
|
override hideResizeHandles: TLShapeUtilFlag<TLArrowShape> = () => true
|
||||||
override hideRotateHandle: TLShapeUtilFlag<TLArrowShape> = () => true
|
override hideRotateHandle: TLShapeUtilFlag<TLArrowShape> = () => true
|
||||||
|
override hideSelectionBoundsBg: TLShapeUtilFlag<TLArrowShape> = () => true
|
||||||
override hideSelectionBoundsFg: TLShapeUtilFlag<TLArrowShape> = () => true
|
override hideSelectionBoundsFg: TLShapeUtilFlag<TLArrowShape> = () => true
|
||||||
|
|
||||||
override getDefaultProps(): TLArrowShape['props'] {
|
override getDefaultProps(): TLArrowShape['props'] {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Editor,
|
||||||
HIT_TEST_MARGIN,
|
HIT_TEST_MARGIN,
|
||||||
StateNode,
|
StateNode,
|
||||||
TLClickEventInfo,
|
TLClickEventInfo,
|
||||||
|
@ -8,7 +9,9 @@ import {
|
||||||
TLShape,
|
TLShape,
|
||||||
TLTextShape,
|
TLTextShape,
|
||||||
Vec2d,
|
Vec2d,
|
||||||
|
VecLike,
|
||||||
createShapeId,
|
createShapeId,
|
||||||
|
pointInPolygon,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import { getHitShapeOnCanvasPointerDown } from '../../selection-logic/getHitShapeOnCanvasPointerDown'
|
import { getHitShapeOnCanvasPointerDown } from '../../selection-logic/getHitShapeOnCanvasPointerDown'
|
||||||
import { getShouldEnterCropMode } from '../../selection-logic/getShouldEnterCropModeOnPointerDown'
|
import { getShouldEnterCropMode } from '../../selection-logic/getShouldEnterCropModeOnPointerDown'
|
||||||
|
@ -60,18 +63,17 @@ export class Idle extends StateNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
onlySelectedShape,
|
||||||
selectedShapeIds,
|
selectedShapeIds,
|
||||||
inputs: { currentPagePoint },
|
inputs: { currentPagePoint },
|
||||||
} = this.editor
|
} = this.editor
|
||||||
|
|
||||||
if (selectedShapeIds.length > 0) {
|
|
||||||
// If there's only one shape selected, and if that shape's
|
|
||||||
// geometry is open, then don't test the selection background
|
|
||||||
if (
|
if (
|
||||||
selectedShapeIds.length > 1 ||
|
selectedShapeIds.length > 1 ||
|
||||||
this.editor.getGeometry(selectedShapeIds[0]).isClosed
|
(onlySelectedShape &&
|
||||||
|
!this.editor.getShapeUtil(onlySelectedShape).hideSelectionBoundsBg(onlySelectedShape))
|
||||||
) {
|
) {
|
||||||
if (this.editor.selectionBounds?.containsPoint(currentPagePoint)) {
|
if (isPointInRotatedSelectionBounds(this.editor, currentPagePoint)) {
|
||||||
this.onPointerDown({
|
this.onPointerDown({
|
||||||
...info,
|
...info,
|
||||||
target: 'selection',
|
target: 'selection',
|
||||||
|
@ -79,7 +81,6 @@ export class Idle extends StateNode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.parent.transition('pointing_canvas', info)
|
this.parent.transition('pointing_canvas', info)
|
||||||
break
|
break
|
||||||
|
@ -511,3 +512,16 @@ export class Idle extends StateNode {
|
||||||
export const MAJOR_NUDGE_FACTOR = 10
|
export const MAJOR_NUDGE_FACTOR = 10
|
||||||
export const MINOR_NUDGE_FACTOR = 1
|
export const MINOR_NUDGE_FACTOR = 1
|
||||||
export const GRID_INCREMENT = 5
|
export const GRID_INCREMENT = 5
|
||||||
|
|
||||||
|
function isPointInRotatedSelectionBounds(editor: Editor, point: VecLike) {
|
||||||
|
const { selectionBounds } = editor
|
||||||
|
if (!selectionBounds) return false
|
||||||
|
|
||||||
|
const { selectionRotation } = editor
|
||||||
|
if (!selectionRotation) return selectionBounds.containsPoint(point)
|
||||||
|
|
||||||
|
return pointInPolygon(
|
||||||
|
point,
|
||||||
|
selectionBounds.corners.map((c) => Vec2d.RotWith(c, selectionBounds.point, selectionRotation))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -69,7 +69,11 @@ export function selectOnCanvasPointerUp(editor: Editor) {
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we clear the selction because the user selected
|
// Otherwise, we clear the selction because the user selected
|
||||||
// nothing instead of their current selection.
|
// nothing instead of their current selection.
|
||||||
|
|
||||||
|
if (selectedShapeIds.length > 0) {
|
||||||
|
editor.mark('selecting none')
|
||||||
editor.selectNone()
|
editor.selectNone()
|
||||||
|
}
|
||||||
|
|
||||||
// If the click was inside of the current focused group, then
|
// If the click was inside of the current focused group, then
|
||||||
// we keep that focused group; otherwise we clear the focused
|
// we keep that focused group; otherwise we clear the focused
|
||||||
|
|
|
@ -195,6 +195,74 @@ describe('when shape is hollow', () => {
|
||||||
editor.pointerUp()
|
editor.pointerUp()
|
||||||
expect(editor.selectedShapeIds).toEqual([])
|
expect(editor.selectedShapeIds).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('drags draw shape child', () => {
|
||||||
|
editor
|
||||||
|
.selectAll()
|
||||||
|
.deleteShapes(editor.selectedShapeIds)
|
||||||
|
.setCurrentTool('draw')
|
||||||
|
.pointerMove(500, 500)
|
||||||
|
.pointerDown()
|
||||||
|
.pointerMove(501, 501)
|
||||||
|
.pointerMove(550, 550)
|
||||||
|
.pointerMove(599, 599)
|
||||||
|
.pointerMove(600, 600)
|
||||||
|
.pointerUp()
|
||||||
|
.selectAll()
|
||||||
|
.setCurrentTool('select')
|
||||||
|
|
||||||
|
expect(editor.selectedShapeIds.length).toBe(1)
|
||||||
|
|
||||||
|
// Not inside of the shape but inside of the selection bounds
|
||||||
|
editor.pointerMove(510, 590)
|
||||||
|
expect(editor.hoveredShapeId).toBe(null)
|
||||||
|
|
||||||
|
// Draw shapes have `hideSelectionBoundsBg` set to false
|
||||||
|
editor.pointerDown()
|
||||||
|
editor.expectToBeIn('select.pointing_selection')
|
||||||
|
editor.pointerUp()
|
||||||
|
|
||||||
|
editor.selectAll()
|
||||||
|
editor.rotateSelection(Math.PI)
|
||||||
|
editor.setCurrentTool('select')
|
||||||
|
editor.pointerMove(590, 510)
|
||||||
|
|
||||||
|
editor.pointerDown()
|
||||||
|
editor.expectToBeIn('select.pointing_selection')
|
||||||
|
editor.pointerUp()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not drag arrow shape', () => {
|
||||||
|
editor
|
||||||
|
.selectAll()
|
||||||
|
.deleteShapes(editor.selectedShapeIds)
|
||||||
|
.setCurrentTool('arrow')
|
||||||
|
.pointerMove(500, 500)
|
||||||
|
.pointerDown()
|
||||||
|
.pointerMove(600, 600)
|
||||||
|
.pointerUp()
|
||||||
|
.selectAll()
|
||||||
|
.setCurrentTool('select')
|
||||||
|
|
||||||
|
expect(editor.selectedShapeIds.length).toBe(1)
|
||||||
|
|
||||||
|
// Not inside of the shape but inside of the selection bounds
|
||||||
|
editor.pointerMove(510, 590)
|
||||||
|
expect(editor.hoveredShapeId).toBe(null)
|
||||||
|
|
||||||
|
// Arrow shapes have `hideSelectionBoundsBg` set to true
|
||||||
|
editor.pointerDown()
|
||||||
|
editor.expectToBeIn('select.pointing_canvas')
|
||||||
|
|
||||||
|
editor.selectAll()
|
||||||
|
editor.rotateSelection(Math.PI)
|
||||||
|
editor.setCurrentTool('select')
|
||||||
|
editor.pointerMove(590, 510)
|
||||||
|
|
||||||
|
editor.pointerDown()
|
||||||
|
editor.expectToBeIn('select.pointing_canvas')
|
||||||
|
editor.pointerUp()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when shape is a frame', () => {
|
describe('when shape is a frame', () => {
|
||||||
|
|
Loading…
Reference in a new issue