A Custom Shape
When the built-ins don’t fit, subclass Shape and implement its three
abstract methods: getLocalBounds() (local extent), drawOps() (backend-neutral
draw ops in local space), and hitTest() ('fill' | 'stroke' | null).
Here a Triangle emits a single closed polygon op and hit-tests its fill with pointInPolygon.
import { Shape, Bounds, pointInPolygon } from '@veyrajs/core'import type { DrawOp, ShapeConfig, ShapeHitKind, Vec2 } from '@veyrajs/core'
interface TriangleConfig extends ShapeConfig { width?: number height?: number}
class Triangle extends Shape { readonly type = 'Triangle' width: number height: number
constructor(config: TriangleConfig = {}) { super(config) this.width = config.width ?? 100 this.height = config.height ?? 80 }
// Three corners in LOCAL space: apex centered on top, base along the bottom. private get points(): Vec2[] { return [ { x: this.width / 2, y: 0 }, { x: this.width, y: this.height }, { x: 0, y: this.height }, ] }
getLocalBounds(): Bounds { return Bounds.fromRect(0, 0, this.width, this.height) }
drawOps(): DrawOp[] { return [{ type: 'polygon', points: this.points, closed: true, ...this.fillStrokeStyle }] }
hitTest(p: Vec2): ShapeHitKind | null { return pointInPolygon(p, this.points) ? 'fill' : null }}
// Use it like any built-in shape (layer comes from stage.createLayer()):layer.add(new Triangle({ x: 120, y: 80, width: 140, height: 110, fill: '#34d399' }))drawOps() spreads the protected fillStrokeStyle bundle, so fill/stroke/strokeWidth from the
config just work; the node transform (x, y, rotation, …) places the shape, so geometry stays in
local space. To make the shape persist, override toObject() to emit width/height and register
a factory on a ClassRegistry — see Advanced → Custom Node Types.
Related
Section titled “Related”- Shapes (API) — the
Shapecontract. - Rendering (API) — the
DrawOpvocabulary. - Math (API) —
Bounds,pointInPolygon. - Advanced → Custom Node Types — serialization for your shape.