ShapeUtil
refactor, Editor
cleanup (#1611)
This PR improves the ergonomics of `ShapeUtil` classes. ### Cached methods First, I've remove the cached methods (such as `bounds`) from the `ShapeUtil` class and lifted this to the `Editor` class. Previously, calling `ShapeUtil.getBounds` would return the un-cached bounds of a shape, while calling `ShapeUtil.bounds` would return the cached bounds of a shape. We also had `Editor.getBounds`, which would call `ShapeUtil.bounds`. It was confusing. The cached methods like `outline` were also marked with "please don't override", which suggested the architecture was just wrong. The only weirdness from this is that utils sometimes reach out to the editor for cached versions of data rather than calling their own cached methods. It's still an easier story to tell than what we had before. ### More defaults We now have three and only three `abstract` methods for a `ShapeUtil`: - `getDefaultProps` (renamed from `defaultProps`) - `getBounds`, - `component` - `indicator` Previously, we also had `getCenter` as an abstract method, though this was usually just the middle of the bounds anyway. ### Editing bounds This PR removes the concept of editingBounds. The viewport will no longer animate to editing shapes. ### Active area manager This PR also removes the active area manager, which was not being used in the way we expected it to be. ### Dpr manager This PR removes the dpr manager and uses a hook instead to update it from React. This is one less runtime browser dependency in the app, one less thing to document. ### Moving things around This PR also continues to try to organize related methods and properties in the editor. ### Change Type - [x] `major` — Breaking change ### Release Notes - [editor] renames `defaultProps` to `getDefaultProps` - [editor] removes `outline`, `outlineSegments`, `handles`, `bounds` - [editor] renames `renderBackground` to `backgroundComponent`
This commit is contained in:
parent
38d74a9ff0
commit
57bb341593
63 changed files with 6422 additions and 6370 deletions
|
@ -11,6 +11,126 @@ keywords:
|
|||
- utils
|
||||
---
|
||||
|
||||
Coming soon.
|
||||
In tldraw, **shapes** are the things that are on the canvas. This article is about shapes: what they are, how they work, and how to create your own shapes. If you'd prefer to see an example, see the tldraw repository's [examples app](https://github.com/tldraw/tldraw/tree/main/apps/examples) for examples of how to create custom shapes in tldraw.
|
||||
|
||||
See the [tldraw repository](https://github.com/tldraw/tldraw/tree/main/apps/examples) for an example of how to create custom shapes in tldraw.
|
||||
## Custom shapes
|
||||
|
||||
Let's create a custom "card" shape.
|
||||
|
||||
### Shape type
|
||||
|
||||
In tldraw's data model, each shape is represented by a JSON object. Let's first create a type that describes what this object will look like.
|
||||
|
||||
```ts
|
||||
import { TLBaseShape } from '@tldraw/tldraw'
|
||||
|
||||
type CardShape = TLBaseShape<
|
||||
'card',
|
||||
{ w: number, h: number }
|
||||
>
|
||||
```
|
||||
|
||||
With the `TLBaseShape` helper, we define the shape's `type` property (`card`) and the shape's `props` property (`{ w: number, h: number }`). The type can be any string but the props must be a regular [JSON-serializable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description) JavaScript object.
|
||||
|
||||
The `TLBaseShape` helper adds the other default properties of a shape, such as `parentId`, `x`, `y`, and `rotation`.
|
||||
|
||||
### Shape Util
|
||||
|
||||
While tldraw's shapes themselves are simple JSON objects, we use `ShapeUtil` classes to answer questions about shapes. For example, when the editor needs to know the bounding box of our card shape, it will find a `ShapeUtil` for the `card` type and call that util's `bounds` method, passing in the `CardShape` object as an argument.
|
||||
|
||||
Let's create a `ShapeUtil` class for the shape.
|
||||
|
||||
```tsx
|
||||
import { ShapeUtil, HTMLContainer } from '@tldraw/tldraw'
|
||||
|
||||
class CardShapeUtil extends ShapeUtil<CardShape> {
|
||||
static type = 'card' as const
|
||||
|
||||
getDefaultProps(): CardShape['props'] {
|
||||
return {
|
||||
w: 100,
|
||||
h: 100,
|
||||
}
|
||||
}
|
||||
|
||||
getBounds(shape: Shape) {
|
||||
return new Box2d(0, 0, shape.props.w, shape.props.h)
|
||||
}
|
||||
|
||||
component(shape: Shape) {
|
||||
return (
|
||||
<HTMLContainer>Hello</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
indicator(shape: Shape) {
|
||||
return (
|
||||
<rect width={shape.props.w} height={shape.props.h}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is a minimal `ShapeUtil`. We've given it a static property `type` that matches the type of our shape, we've provided implementations for the abstract methods `getDefaultProps`, `getBounds`, `component`, and `indicator`.
|
||||
|
||||
We still have work to do on the `CardShapeUtil` class, but we'll come back to it later. For now, let's put the shape onto the canvas by passing it to the `<Tldraw>` component.
|
||||
|
||||
### Defining the shape
|
||||
|
||||
Before we pass the shape down, we need to package it up in a way using the `defineShape` function. We can then create an array of our defined shapes and pass them into the `<Tldraw>` component's `shapes` prop.
|
||||
|
||||
```tsx
|
||||
import { Tldraw } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/tldraw.css'
|
||||
|
||||
const MyCardShape = defineShape('card', { util: CardShapeUtil })
|
||||
const MyCustomShapes = [MyCardShape]
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<Tldraw shapes={MyCustomShapes}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `defineShape` function can also be used to include a tool that we can use to create this type of shape. For now, let's create it using the `Editor` API.
|
||||
|
||||
```tsx
|
||||
export default function () {
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<Tldraw shapes={MyCustomShapes} onMount={editor => {
|
||||
editor.createShapes([{ type: "card" }])
|
||||
}}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Once the page refreshes, we should now have our custom shape on the canvas.
|
||||
|
||||
## Using starter shapes
|
||||
|
||||
You can use "starter" shape utils like `BaseBoxShapeUtil` to get regular rectangular shape behavior.
|
||||
|
||||
> todo
|
||||
|
||||
## Flags
|
||||
|
||||
You can use flags like `hideRotateHandle` to hide different parts of the UI when the shape is selected, or else to control different behaviors of the shape.
|
||||
|
||||
> todo
|
||||
|
||||
## Interaction
|
||||
|
||||
You can turn on `pointer-events` to allow users to interact inside of the shape.
|
||||
|
||||
> todo
|
||||
|
||||
## Editing
|
||||
|
||||
You can make shapes "editable" to help decide when they're interactive or not.
|
||||
|
||||
> todo
|
Loading…
Add table
Add a link
Reference in a new issue