Drawing

Shapes, paint, groups and hit testing.

App.tsx
1import { Canvas, Circle, Path, RoundedRect } from 'react-native-tgfx'
2
3export function Shapes() {
4 return (
5 <Canvas style={{ flex: 1 }}>
6 <RoundedRect x={40} y={40} width={150} height={150}
7 r={28} color="#7c3aed" />
8 <Circle cx={290} cy={115} r={75}
9 color="#f59e0b" />
10 <Path path={wave} style="stroke" strokeWidth={14}
11 color="#0ea5e9" />
12 </Canvas>
13 )
14}
9:41

Draw in a Canvas, or without React, in a Scene:

import { Canvas, Rect } from 'react-native-tgfx'

<Canvas style={{ width: 300, height: 140 }}>
  <Rect x={0} y={0} width={300} height={140} color="#101725" />
</Canvas>

Each component has a lowercase factory (Rect → rect). See Imperative.

Shapes

Paint

Props inline, or a Paint child (PaintProps):

<Circle cx={70} cy={70} r={40}>
  <Paint color="#f2c088" style="stroke" strokeWidth={3} />
</Circle>

Color accepts names, hex, rgb()/rgba(), hsl()/hsla(), 0xAARRGGBB, [r, g, b, a] in 0..1, and color():

<Rect color="color(display-p3 1 0.2 0.1)" />
<Rect color="color(srgb 1 0 0 / 0.5)" />

Group

GroupProps. Paint and Transform inherit down.

<Group transform={[{ translate: [40, 0] }, { rotate: '12deg' }, { scale: 1.5 }]}>
  <Rect x={0} y={0} width={40} height={40} />
</Group>
<Group clip={{ x: 0, y: 0, width: 100, height: 100, r: 12 }}>
  <TgfxImage x={0} y={0} width={100} height={100} image={photo} />
</Group>

clip takes a ClipRect or SVG path data. invertClip keeps the outside instead. origin sets the transform pivot.

layer applies opacity to the group as a whole:

<Group layer opacity={0.5}>
  <Circle cx={40} cy={40} r={30} color="#4F8EF7" />
  <Circle cx={70} cy={40} r={30} color="#4F8EF7" />
</Group>

picture records a static subtree once and replays it.

<Group picture>
  <Path path={gridPath} color="#e5e5ea" style="stroke" />
</Group>

3D transforms

transform also takes perspective, rotateX, rotateY and translateZ. Put perspective before the rotations it applies to.

preserve3d sorts child groups by depth. clip, layer, picture, <FilmGrain> or an export flattens it.

<Group
  preserve3d
  transform={[{ translate: [150, 150] }, { perspective: 900 }, { rotateY: turn }]}
>
  <Group transform={[{ translateZ: 50 }]}>
    <Rect x={-50} y={-50} width={100} height={100} color="#0a84ff" />
  </Group>
  <Group transform={[{ rotateY: '90deg' }, { translateZ: 50 }]}>
    <Rect x={-50} y={-50} width={100} height={100} color="#ff3b30" />
  </Group>
</Group>

Atlas and meshes

<Atlas
  image={sheet}
  sprites={particles.map((p) => ({
    rect: { x: 0, y: 0, width: 16, height: 16 },
    x: p.x,
    y: p.y,
    scale: p.size,
  }))}
/>
<Vertices
  vertices={[
    { x: 0, y: 0 },
    { x: 200, y: 0 },
    { x: 0, y: 200 },
    { x: 200, y: 200 },
  ]}
  colors={['#ff0000', '#00ff00', '#0000ff', '#ffff00']}
  indices={[0, 1, 2, 1, 2, 3]}
/>

Viewport

<Canvas viewport={{ zoom, x, y }}> zooms and pans the whole scene. For gestures, use CanvasHandle.setViewport; see Zoom and pan. Headless, use surface.setViewport.

Hit testing

CanvasHandle.hitTest returns HitTestResult topmost first.

const canvas = useRef<CanvasHandle>(null)
const bar = useNode()

<GestureDetector gesture={Gesture.Tap().onEnd((e) => {
  const hits = canvas.current?.hitTest(e.x, e.y) ?? []
  if (hits.some((hit) => hit.node === bar)) select(bar)
})}>
  <Canvas ref={canvas} style={{ flex: 1 }}>
    <Rect ref={bar} x={10} y={10} width={40} height={80} color="#0a84ff" />
  </Canvas>
</GestureDetector>

useCanvasPress does the same with the responder system:

const onTouch = useCanvasPress(canvas, [[bar, () => select(bar)]], {
  precise: true,
  onPress: ({ x, y, nodes }) => track(x, y, nodes),
})

<View onStartShouldSetResponder={() => true} onResponderRelease={onTouch}>
  <Canvas ref={canvas}>
    <Rect ref={bar} x={10} y={10} width={40} height={80} color="#0a84ff" />
  </Canvas>
</View>

Options are CanvasPressOptions. precise tests the filled shape, not its bounds. onPress gets a CanvasPress for every press, even on empty space.

Headless, use dispatchPress with Scene.handlesAt:

dispatchPress(scene.handlesAt(surface.hitTest(x, y, true)), [
  [bar, () => select(bar)],
])

On this page