From 52ae47371d1825f7ed0172fad54558222b3faf15 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Tue, 7 Dec 2021 20:48:56 +0000 Subject: [PATCH] [improvement] Select unfilled shapes by clicking on their stroke (#438) * removes touch events from middle of shapes * Improve ellipse * selectable stroke when not selected, fill when selected * Update BrushSession.spec.ts * Fix test --- apps/www/next-env.d.ts | 1 - packages/core/src/TLShapeUtil/TLShapeUtil.tsx | 10 - packages/core/src/hooks/useStyle.tsx | 23 ++ .../BrushSession/BrushSession.spec.ts | 4 +- .../src/state/shapes/ArrowUtil/ArrowUtil.tsx | 77 +++---- .../src/state/shapes/DrawUtil/DrawUtil.tsx | 198 +++++++++--------- .../state/shapes/EllipseUtil/EllipseUtil.tsx | 37 +++- .../shapes/RectangleUtil/RectangleUtil.tsx | 65 +++--- 8 files changed, 230 insertions(+), 185 deletions(-) diff --git a/apps/www/next-env.d.ts b/apps/www/next-env.d.ts index 9bc3dd46b..4f11a03dc 100644 --- a/apps/www/next-env.d.ts +++ b/apps/www/next-env.d.ts @@ -1,5 +1,4 @@ /// -/// /// // NOTE: This file should not be edited diff --git a/packages/core/src/TLShapeUtil/TLShapeUtil.tsx b/packages/core/src/TLShapeUtil/TLShapeUtil.tsx index 99fee77d7..95c57ca86 100644 --- a/packages/core/src/TLShapeUtil/TLShapeUtil.tsx +++ b/packages/core/src/TLShapeUtil/TLShapeUtil.tsx @@ -39,17 +39,7 @@ export abstract class TLShapeUtil { const shapeBounds = this.getBounds(shape) - - if (!shape.rotation) { - return ( - Utils.boundsContain(bounds, shapeBounds) || - Utils.boundsContain(shapeBounds, bounds) || - Utils.boundsCollide(shapeBounds, bounds) - ) - } - const corners = Utils.getRotatedCorners(shapeBounds, shape.rotation) - return ( corners.every((point) => Utils.pointInBounds(point, bounds)) || intersectPolylineBounds(corners, bounds).length > 0 diff --git a/packages/core/src/hooks/useStyle.tsx b/packages/core/src/hooks/useStyle.tsx index 01f63e004..6267f369c 100644 --- a/packages/core/src/hooks/useStyle.tsx +++ b/packages/core/src/hooks/useStyle.tsx @@ -218,6 +218,26 @@ const tlcss = css` contain: layout style size; } + .tl-stroke-hitarea { + cursor: pointer; + fill: none; + stroke: transparent; + stroke-width: calc(24px * var(--tl-scale)); + pointer-events: stroke; + stroke-linecap: round; + stroke-linejoin: round; + } + + .tl-fill-hitarea { + cursor: pointer; + fill: transparent; + stroke: transparent; + stroke-width: calc(24px * var(--tl-scale)); + pointer-events: all; + stroke-linecap: round; + stroke-linejoin: round; + } + .tl-counter-scaled { transform: scale(var(--tl-scale)); } @@ -354,6 +374,7 @@ const tlcss = css` .tl-handle { pointer-events: all; + cursor: grab; } .tl-handle:hover .tl-handle-bg { @@ -365,6 +386,7 @@ const tlcss = css` } .tl-handle:active .tl-handle-bg { + cursor: grabbing; fill: var(--tl-selectFill); } @@ -389,6 +411,7 @@ const tlcss = css` stroke-width: calc(3px * var(--tl-scale)); fill: var(--tl-selectFill); stroke: var(--tl-selected); + pointer-events: none; } .tl-centered-g { diff --git a/packages/tldraw/src/state/sessions/BrushSession/BrushSession.spec.ts b/packages/tldraw/src/state/sessions/BrushSession/BrushSession.spec.ts index 196bb49c2..434be1b8a 100644 --- a/packages/tldraw/src/state/sessions/BrushSession/BrushSession.spec.ts +++ b/packages/tldraw/src/state/sessions/BrushSession/BrushSession.spec.ts @@ -6,9 +6,9 @@ describe('Brush session', () => { const app = new TldrawTestApp() .loadDocument(mockDocument) .selectNone() - .movePointer([-10, -10]) + .movePointer([-48, -48]) .startSession(SessionType.Brush) - .movePointer([10, 10]) + .movePointer([48, 48]) .completeSession() expect(app.status).toBe(TDStatus.Idle) expect(app.selectedIds.length).toBe(1) diff --git a/packages/tldraw/src/state/shapes/ArrowUtil/ArrowUtil.tsx b/packages/tldraw/src/state/shapes/ArrowUtil/ArrowUtil.tsx index 78450649f..684a19d8b 100644 --- a/packages/tldraw/src/state/shapes/ArrowUtil/ArrowUtil.tsx +++ b/packages/tldraw/src/state/shapes/ArrowUtil/ArrowUtil.tsx @@ -144,16 +144,7 @@ export class ArrowUtil extends TDShapeUtil { shaftPath = arrowDist > 2 ? ( <> - + { // Curved arrow path shaftPath = ( <> - + { strokeDashoffset={strokeDashoffset} strokeLinecap="round" strokeLinejoin="round" - pointerEvents="stroke" + pointerEvents="none" /> ) @@ -238,30 +219,38 @@ export class ArrowUtil extends TDShapeUtil { {shaftPath} {startArrowHead && ( - + <> + + + )} {endArrowHead && ( - + <> + + + )} diff --git a/packages/tldraw/src/state/shapes/DrawUtil/DrawUtil.tsx b/packages/tldraw/src/state/shapes/DrawUtil/DrawUtil.tsx index 3ebde7129..1952264fe 100644 --- a/packages/tldraw/src/state/shapes/DrawUtil/DrawUtil.tsx +++ b/packages/tldraw/src/state/shapes/DrawUtil/DrawUtil.tsx @@ -51,121 +51,131 @@ export class DrawUtil extends TDShapeUtil { ) } - Component = TDShapeUtil.Component(({ shape, meta, isGhost, events }, ref) => { - const { points, style, isComplete } = shape + Component = TDShapeUtil.Component( + ({ shape, meta, isSelected, isGhost, events }, ref) => { + const { points, style, isComplete } = shape - const polygonPathTDSnapshot = React.useMemo(() => { - return getFillPath(shape) - }, [points, style.size]) + const polygonPathTDSnapshot = React.useMemo(() => { + return getFillPath(shape) + }, [points, style.size]) - const pathTDSnapshot = React.useMemo(() => { - return style.dash === DashStyle.Draw - ? getDrawStrokePathTDSnapshot(shape) - : getSolidStrokePathTDSnapshot(shape) - }, [points, style.size, style.dash, isComplete]) + const pathTDSnapshot = React.useMemo(() => { + return style.dash === DashStyle.Draw + ? getDrawStrokePathTDSnapshot(shape) + : getSolidStrokePathTDSnapshot(shape) + }, [points, style.size, style.dash, isComplete]) - const styles = getShapeStyle(style, meta.isDarkMode) - const { stroke, fill, strokeWidth } = styles + const styles = getShapeStyle(style, meta.isDarkMode) + const { stroke, fill, strokeWidth } = styles - // For very short lines, draw a point instead of a line - const bounds = this.getBounds(shape) + // For very short lines, draw a point instead of a line + const bounds = this.getBounds(shape) - const verySmall = bounds.width <= strokeWidth / 2 && bounds.height <= strokeWidth / 2 + const verySmall = bounds.width <= strokeWidth / 2 && bounds.height <= strokeWidth / 2 - if (verySmall) { - const sw = 1 + strokeWidth + if (verySmall) { + const sw = 1 + strokeWidth - return ( - - - - ) - } + return ( + + + + ) + } - const shouldFill = - style.isFilled && - points.length > 3 && - Vec.dist(points[0], points[points.length - 1]) < strokeWidth * 2 + const shouldFill = + style.isFilled && + points.length > 3 && + Vec.dist(points[0], points[points.length - 1]) < strokeWidth * 2 + + if (shape.style.dash === DashStyle.Draw) { + return ( + + + + {shouldFill && ( + + )} + + + + ) + } + + // For solid, dash and dotted lines, draw a regular stroke path + + const strokeDasharray = { + [DashStyle.Draw]: 'none', + [DashStyle.Solid]: `none`, + [DashStyle.Dotted]: `0.1 ${strokeWidth * 4}`, + [DashStyle.Dashed]: `${strokeWidth * 4} ${strokeWidth * 4}`, + }[style.dash] + + const strokeDashoffset = { + [DashStyle.Draw]: 'none', + [DashStyle.Solid]: `none`, + [DashStyle.Dotted]: `0`, + [DashStyle.Dashed]: `0`, + }[style.dash] + + const sw = 1 + strokeWidth * 1.5 - if (shape.style.dash === DashStyle.Draw) { return ( - {shouldFill && ( - - )} + + ) } - - // For solid, dash and dotted lines, draw a regular stroke path - - const strokeDasharray = { - [DashStyle.Draw]: 'none', - [DashStyle.Solid]: `none`, - [DashStyle.Dotted]: `0.1 ${strokeWidth * 4}`, - [DashStyle.Dashed]: `${strokeWidth * 4} ${strokeWidth * 4}`, - }[style.dash] - - const strokeDashoffset = { - [DashStyle.Draw]: 'none', - [DashStyle.Solid]: `none`, - [DashStyle.Dotted]: `0`, - [DashStyle.Dashed]: `0`, - }[style.dash] - - const sw = 1 + strokeWidth * 1.5 - - return ( - - - - - - - ) - }) + ) Indicator = TDShapeUtil.Indicator(({ shape }) => { const { points } = shape diff --git a/packages/tldraw/src/state/shapes/EllipseUtil/EllipseUtil.tsx b/packages/tldraw/src/state/shapes/EllipseUtil/EllipseUtil.tsx index d86711907..62fe7588a 100644 --- a/packages/tldraw/src/state/shapes/EllipseUtil/EllipseUtil.tsx +++ b/packages/tldraw/src/state/shapes/EllipseUtil/EllipseUtil.tsx @@ -39,7 +39,7 @@ export class EllipseUtil extends TDShapeUtil { } Component = TDShapeUtil.Component( - ({ shape, isGhost, isBinding, meta, events }, ref) => { + ({ shape, isGhost, isSelected, isBinding, meta, events }, ref) => { const { radius: [radiusX, radiusY], style, @@ -68,18 +68,25 @@ export class EllipseUtil extends TDShapeUtil { ry={ry + 2} /> )} + { /> )} + @@ -130,7 +144,16 @@ export class EllipseUtil extends TDShapeUtil { ) Indicator = TDShapeUtil.Indicator(({ shape }) => { - return + const { + radius: [radiusX, radiusY], + style: { dash }, + } = shape + + return dash === DashStyle.Draw ? ( + + ) : ( + + ) }) hitTestPoint = (shape: T, point: number[]): boolean => { diff --git a/packages/tldraw/src/state/shapes/RectangleUtil/RectangleUtil.tsx b/packages/tldraw/src/state/shapes/RectangleUtil/RectangleUtil.tsx index 4f52f211d..6bf88e425 100644 --- a/packages/tldraw/src/state/shapes/RectangleUtil/RectangleUtil.tsx +++ b/packages/tldraw/src/state/shapes/RectangleUtil/RectangleUtil.tsx @@ -41,7 +41,7 @@ export class RectangleUtil extends TDShapeUtil { } Component = TDShapeUtil.Component( - ({ shape, isBinding, isGhost, meta, events }, ref) => { + ({ shape, isBinding, isSelected, isGhost, meta, events }, ref) => { const { id, size, style } = shape const styles = getShapeStyle(style, meta.isDarkMode) @@ -50,6 +50,7 @@ export class RectangleUtil extends TDShapeUtil { if (style.dash === DashStyle.Draw) { const pathTDSnapshot = getRectanglePath(shape) + const indicatorPath = getRectangleIndicatorPathTDSnapshot(shape) return ( @@ -63,18 +64,20 @@ export class RectangleUtil extends TDShapeUtil { /> )} + @@ -107,9 +110,6 @@ export class RectangleUtil extends TDShapeUtil { y1={start[1]} x2={end[0]} y2={end[1]} - stroke={styles.stroke} - strokeWidth={sw} - strokeLinecap="round" strokeDasharray={strokeDasharray} strokeDashoffset={strokeDashoffset} /> @@ -118,26 +118,37 @@ export class RectangleUtil extends TDShapeUtil { return ( - {isBinding && ( + + {isBinding && ( + + )} - )} - - {paths} + {style.isFilled && ( + + )} + + {paths} + + ) }