diff --git a/apps/examples/src/examples/selection-ui/README.md b/apps/examples/src/examples/selection-ui/README.md new file mode 100644 index 000000000..e3718ced8 --- /dev/null +++ b/apps/examples/src/examples/selection-ui/README.md @@ -0,0 +1,9 @@ +--- +title: Selection UI +component: ./SelectionUiExample.tsx +category: shapes/tools +--- + +--- + +You can use the `InFrontOfTheCanvas` component to show extra user interface elements around the user's selection. diff --git a/apps/examples/src/examples/selection-ui/SelectionUiExample.tsx b/apps/examples/src/examples/selection-ui/SelectionUiExample.tsx new file mode 100644 index 000000000..905a7873f --- /dev/null +++ b/apps/examples/src/examples/selection-ui/SelectionUiExample.tsx @@ -0,0 +1,135 @@ +import { + TLComponents, + Tldraw, + Vec, + intersectLineSegmentPolygon, + stopEventPropagation, + useEditor, + useValue, +} from 'tldraw' +import 'tldraw/tldraw.css' + +const components: TLComponents = { + InFrontOfTheCanvas: () => { + const editor = useEditor() + + const info = useValue( + 'selection bounds', + () => { + const screenBounds = editor.getViewportScreenBounds() + const rotation = editor.getSelectionRotation() + const rotatedScreenBounds = editor.getSelectionRotatedScreenBounds() + if (!rotatedScreenBounds) return + return { + // we really want the position within the + // tldraw component's bounds, not the screen itself + x: rotatedScreenBounds.x - screenBounds.x, + y: rotatedScreenBounds.y - screenBounds.y, + width: rotatedScreenBounds.width, + height: rotatedScreenBounds.height, + rotation: rotation, + } + }, + [editor] + ) + + if (!info) return + + return ( +
+ + + + +
+ ) + }, +} + +export default function BasicExample() { + return ( +
+ +
+ ) +} + +/** + * This button will duplicate the editor's current selected shapes in + * a certain direction. Its rotation determines the appearance of the + * button (its actual css rotation) as well as the direction in which + * the duplicated shapes are offset from the original shapes. It's + * zeroed to the right. + */ +function DuplicateInDirectionButton({ + x, + y, + rotation, +}: { + x: number + y: number + rotation: number +}) { + const editor = useEditor() + + return ( + + ) +} diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index ae3f7136b..e52e7db5a 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -726,6 +726,7 @@ export class Editor extends EventEmitter { getSelectedShapes(): TLShape[]; getSelectionPageBounds(): Box | null; getSelectionRotatedPageBounds(): Box | undefined; + getSelectionRotatedScreenBounds(): Box | undefined; getSelectionRotation(): number; getShape(shape: TLParentId | TLShape): T | undefined; getShapeAncestors(shape: TLShape | TLShapeId, acc?: TLShape[]): TLShape[]; diff --git a/packages/editor/api/api.json b/packages/editor/api/api.json index 063898148..2d4099197 100644 --- a/packages/editor/api/api.json +++ b/packages/editor/api/api.json @@ -12182,6 +12182,42 @@ "isAbstract": false, "name": "getSelectionRotatedPageBounds" }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#getSelectionRotatedScreenBounds:member(1)", + "docComment": "/**\n * The bounds of the selection bounding box in the current page space.\n *\n * @readonly @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "getSelectionRotatedScreenBounds(): " + }, + { + "kind": "Reference", + "text": "Box", + "canonicalReference": "@tldraw/editor!Box:class" + }, + { + "kind": "Content", + "text": " | undefined" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 3 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "getSelectionRotatedScreenBounds" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#getSelectionRotation:member(1)", diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index cd30560cf..6bebbc141 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -1661,6 +1661,7 @@ export class Editor extends EventEmitter { } return 0 } + /** * The bounds of the selection bounding box in the current page space. * @@ -1701,6 +1702,20 @@ export class Editor extends EventEmitter { return boxFromRotatedVertices } + /** + * The bounds of the selection bounding box in the current page space. + * + * @readonly + * @public + */ + @computed getSelectionRotatedScreenBounds(): Box | undefined { + const bounds = this.getSelectionRotatedPageBounds() + if (!bounds) return undefined + const { x, y } = this.pageToScreen(bounds.point) + const zoom = this.getZoomLevel() + return new Box(x, y, bounds.width * zoom, bounds.height * zoom) + } + // Focus Group /** @@ -2848,7 +2863,7 @@ export class Editor extends EventEmitter { * @public */ pageToScreen(point: VecLike) { - const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)! + const screenBounds = this.getViewportScreenBounds() const { x: cx, y: cy, z: cz = 1 } = this.getCamera() return {