Expand selection outline for single-selected draw shape (#1379)
Expand the selection outline on draw shapes according to pen thickness when only one shape is selected. ![Kapture 2023-05-15 at 16 20 01](https://github.com/tldraw/tldraw/assets/1489520/373f0ec1-f43d-46c9-9729-0c84aaf2564b) Right now the outline of many of our shapes don't take stroke thickness into account. This is a pretty hard thing to get right, so in the short term here's a fix for one of the most common places this is an issue: selecting a single horizontal/vertical draw shape. This fix isn't perfect: resizing gets slightly janky when you completely flip the shape - see how the handle leaves the cursor behind in the gif when that happens. We can revisit with a more comprehensive solution later. This is pulled out from the highlighter work! The highlighter shape will use the shape APIs added here. ### Change Type - [x] `patch` — Bug Fix ### Test Plan 1. Create a draw shape 2. Select it 3. Selection bounds should include the stroke width 4. Add another shape to the selection 5. Selection bounds should no longer include the stroke width ### Release Notes - Improve selection outlines around horizontal or vertical draw shapes
This commit is contained in:
parent
2c8b431c1f
commit
cd8c92779d
5 changed files with 39 additions and 13 deletions
|
@ -1864,6 +1864,8 @@ export class TLDrawUtil extends TLShapeUtil<TLDrawShape> {
|
|||
// (undocumented)
|
||||
defaultProps(): TLDrawShape['props'];
|
||||
// (undocumented)
|
||||
expandSelectionOutlinePx(shape: TLDrawShape): number;
|
||||
// (undocumented)
|
||||
getBounds(shape: TLDrawShape): Box2d;
|
||||
// (undocumented)
|
||||
getCenter(shape: TLDrawShape): Vec2d;
|
||||
|
@ -2513,6 +2515,8 @@ export abstract class TLShapeUtil<T extends TLUnknownShape> {
|
|||
canUnmount: TLShapeUtilFlag<T>;
|
||||
center(shape: T): Vec2dModel;
|
||||
abstract defaultProps(): T['props'];
|
||||
// @internal (undocumented)
|
||||
expandSelectionOutlinePx(shape: T): number;
|
||||
protected abstract getBounds(shape: T): Box2d;
|
||||
abstract getCenter(shape: T): Vec2dModel;
|
||||
getEditingBounds: (shape: T) => Box2d;
|
||||
|
|
|
@ -303,6 +303,11 @@ export class TLDrawUtil extends TLShapeUtil<TLDrawShape> {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
expandSelectionOutlinePx(shape: TLDrawShape): number {
|
||||
const multiplier = shape.props.dash === 'draw' ? 1.6 : 1
|
||||
return (this.app.getStrokeWidth(shape.props.size) * multiplier) / 2
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -369,6 +369,11 @@ export abstract class TLShapeUtil<T extends TLUnknownShape> {
|
|||
return false
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
expandSelectionOutlinePx(shape: T): number {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,14 +25,24 @@ export const SelectionFg = track(function SelectionFg() {
|
|||
const isDefaultCursor = !app.isMenuOpen && app.cursor.type === 'default'
|
||||
const isCoarsePointer = app.isCoarsePointer
|
||||
|
||||
const bounds = app.selectionBounds
|
||||
let bounds = app.selectionBounds
|
||||
const shapes = app.selectedShapes
|
||||
const onlyShape = shapes.length === 1 ? shapes[0] : null
|
||||
|
||||
useTransform(rSvg, bounds?.x, bounds?.y, 1, app.selectionRotation)
|
||||
// if all shapes have an expandBy for the selection outline, we can expand by the l
|
||||
const expandOutlineBy = onlyShape
|
||||
? app.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape)
|
||||
: 0
|
||||
|
||||
useTransform(rSvg, bounds?.x, bounds?.y, 1, app.selectionRotation, {
|
||||
x: -expandOutlineBy,
|
||||
y: -expandOutlineBy,
|
||||
})
|
||||
|
||||
if (!bounds) return null
|
||||
bounds = bounds.clone().expandBy(expandOutlineBy)
|
||||
|
||||
const zoom = app.zoomLevel
|
||||
const shapes = app.selectedShapes
|
||||
const rotation = app.selectionRotation
|
||||
const isChangingStyles = app.isChangingStyle
|
||||
|
||||
|
@ -54,8 +64,6 @@ export const SelectionFg = track(function SelectionFg() {
|
|||
const targetSizeX = (isSmallX ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
|
||||
const targetSizeY = (isSmallY ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
|
||||
|
||||
const onlyShape = shapes.length === 1 ? shapes[0] : null
|
||||
|
||||
const showSelectionBounds =
|
||||
(onlyShape ? !app.getShapeUtil(onlyShape).hideSelectionBoundsFg(onlyShape) : true) &&
|
||||
!isChangingStyles
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { VecLike } from '@tldraw/primitives'
|
||||
import { useLayoutEffect } from 'react'
|
||||
|
||||
export function useTransform(
|
||||
|
@ -5,21 +6,24 @@ export function useTransform(
|
|||
x?: number,
|
||||
y?: number,
|
||||
scale?: number,
|
||||
rotate?: number
|
||||
rotate?: number,
|
||||
additionalOffset?: VecLike
|
||||
) {
|
||||
useLayoutEffect(() => {
|
||||
const elm = ref.current
|
||||
if (!elm) return
|
||||
if (x === undefined) return
|
||||
|
||||
let trans = `translate(${x}px, ${y}px)`
|
||||
if (scale !== undefined) {
|
||||
trans += ` scale(${scale})`
|
||||
}
|
||||
if (rotate !== undefined) {
|
||||
elm.style.transform = `translate(${x}px, ${y}px) scale(${scale}) rotate(${rotate}rad)`
|
||||
} else {
|
||||
elm.style.transform = `translate(${x}px, ${y}px) scale(${scale})`
|
||||
trans += ` rotate(${rotate}rad)`
|
||||
}
|
||||
} else {
|
||||
elm.style.transform = `translate(${x}px, ${y}px)`
|
||||
if (additionalOffset) {
|
||||
trans += ` translate(${additionalOffset.x}px, ${additionalOffset.y}px)`
|
||||
}
|
||||
elm.style.transform = trans
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue