[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:
Steve Ruiz 2023-07-26 16:32:33 +01:00 committed by GitHub
parent 56cf77a8cd
commit 28b92c5e76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 22 deletions

View file

@ -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>;

View file

@ -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.
* *

View file

@ -99,13 +99,10 @@ 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 = false
shouldDisplayBox && }
IS_FIREFOX
) {
shouldDisplayBox = false
} }
const showCropHandles = const showCropHandles =

View file

@ -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'] {

View file

@ -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,24 +63,22 @@ export class Idle extends StateNode {
} }
const { const {
onlySelectedShape,
selectedShapeIds, selectedShapeIds,
inputs: { currentPagePoint }, inputs: { currentPagePoint },
} = this.editor } = this.editor
if (selectedShapeIds.length > 0) { if (
// If there's only one shape selected, and if that shape's selectedShapeIds.length > 1 ||
// geometry is open, then don't test the selection background (onlySelectedShape &&
if ( !this.editor.getShapeUtil(onlySelectedShape).hideSelectionBoundsBg(onlySelectedShape))
selectedShapeIds.length > 1 || ) {
this.editor.getGeometry(selectedShapeIds[0]).isClosed if (isPointInRotatedSelectionBounds(this.editor, currentPagePoint)) {
) { this.onPointerDown({
if (this.editor.selectionBounds?.containsPoint(currentPagePoint)) { ...info,
this.onPointerDown({ target: 'selection',
...info, })
target: 'selection', return
})
return
}
} }
} }
@ -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))
)
}

View file

@ -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.
editor.selectNone()
if (selectedShapeIds.length > 0) {
editor.mark('selecting none')
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

View file

@ -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', () => {