Skip to main content

CanvasMode Enum

Defines the different interaction modes available on the canvas. These modes determine how user input is interpreted and what actions can be performed.
enum CanvasMode {
  None,
  Pressing,
  SelectionNet,
  Translating,
  Inserting,
  Resizing,
  Pencil,
}
None
0
Default idle state with no active interaction
Pressing
1
User has pressed down but hasn’t initiated a specific action yet
SelectionNet
2
Drawing a selection rectangle to select multiple layers
Translating
3
Moving/dragging selected layers across the canvas
Inserting
4
Inserting a new layer onto the canvas
Resizing
5
Resizing a selected layer by dragging resize handles
Pencil
6
Drawing free-form paths with the pencil tool

CanvasState Type

A discriminated union type that represents the current state of the canvas. Each mode can carry additional data specific to that interaction.
type CanvasState =
  | { mode: CanvasMode.None }
  | { mode: CanvasMode.SelectionNet; origin: Point; current?: Point }
  | { mode: CanvasMode.Translating; current: Point }
  | {
      mode: CanvasMode.Inserting;
      layerType:
        | LayerType.Ellipse
        | LayerType.Rectangle
        | LayerType.Text
        | LayerType.Note;
    }
  | { mode: CanvasMode.Pencil }
  | { mode: CanvasMode.Pressing; origin: Point }
  | { mode: CanvasMode.Resizing; initialBounds: XYWH; corner: Side };

None State

{ mode: CanvasMode.None }
mode
CanvasMode.None
required
No active interaction

SelectionNet State

{ mode: CanvasMode.SelectionNet; origin: Point; current?: Point }
mode
CanvasMode.SelectionNet
required
Drawing selection rectangle
origin
Point
required
Starting point where the selection began. See Point type.
current
Point
Current pointer position while dragging the selection net

Translating State

{ mode: CanvasMode.Translating; current: Point }
mode
CanvasMode.Translating
required
Moving selected layers
current
Point
required
Current pointer position during the drag operation. See Point type.

Inserting State

{
  mode: CanvasMode.Inserting;
  layerType:
    | LayerType.Ellipse
    | LayerType.Rectangle
    | LayerType.Text
    | LayerType.Note;
}
mode
CanvasMode.Inserting
required
Inserting a new layer
layerType
LayerType
required
The type of layer being inserted. Can be Ellipse, Rectangle, Text, or Note. Path layers are created via Pencil mode.

Pencil State

{ mode: CanvasMode.Pencil }
mode
CanvasMode.Pencil
required
Drawing free-form paths

Pressing State

{ mode: CanvasMode.Pressing; origin: Point }
mode
CanvasMode.Pressing
required
Pointer is down but action not yet determined
origin
Point
required
Point where the press started. See Point type.

Resizing State

{ mode: CanvasMode.Resizing; initialBounds: XYWH; corner: Side }
mode
CanvasMode.Resizing
required
Resizing a layer
initialBounds
XYWH
required
The layer’s dimensions before resizing began. See XYWH type.
corner
Side
required
Which corner or edge is being dragged. See Side enum.

Usage Examples

State Management

import { useState } from 'react';
import { CanvasMode, CanvasState } from '@/types/canvas';

const [canvasState, setCanvasState] = useState<CanvasState>({
  mode: CanvasMode.None,
});

Handling Pointer Events

import { CanvasMode, Point } from '@/types/canvas';

function onPointerDown(e: React.PointerEvent) {
  const point: Point = pointerEventToCanvasPoint(e, camera);
  
  if (canvasState.mode === CanvasMode.Inserting) {
    return;
  }
  
  setCanvasState({ 
    mode: CanvasMode.Pressing, 
    origin: point 
  });
}

Mode Transitions

import { CanvasMode, CanvasState } from '@/types/canvas';

function onPointerMove(current: Point) {
  if (canvasState.mode === CanvasMode.Pressing) {
    // Transition to SelectionNet if movement detected
    startMultiSelection(current, canvasState.origin);
  } else if (canvasState.mode === CanvasMode.SelectionNet) {
    updateSelectionNet(current, canvasState.origin);
  } else if (canvasState.mode === CanvasMode.Translating) {
    translateSelectedLayers(current);
  } else if (canvasState.mode === CanvasMode.Resizing) {
    resizeSelectedLayer(current);
  }
}

Inserting Layers

import { CanvasMode, LayerType } from '@/types/canvas';

function startInsertingRectangle() {
  setCanvasState({
    mode: CanvasMode.Inserting,
    layerType: LayerType.Rectangle,
  });
}

function onPointerUp(e: React.PointerEvent) {
  const point = pointerEventToCanvasPoint(e, camera);
  
  if (canvasState.mode === CanvasMode.Inserting) {
    insertLayer(canvasState.layerType, point);
  } else {
    setCanvasState({ mode: CanvasMode.None });
  }
}

Resizing with Initial Bounds

import { CanvasMode, XYWH, Side } from '@/types/canvas';

function onResizeHandlePointerDown(corner: Side, initialBounds: XYWH) {
  history.pause();
  setCanvasState({
    mode: CanvasMode.Resizing,
    initialBounds,
    corner,
  });
}

Type Guards

import { CanvasState, CanvasMode } from '@/types/canvas';

function isTranslating(state: CanvasState): state is { mode: CanvasMode.Translating; current: Point } {
  return state.mode === CanvasMode.Translating;
}

function isInserting(state: CanvasState): state is { mode: CanvasMode.Inserting; layerType: LayerType } {
  return state.mode === CanvasMode.Inserting;
}

Build docs developers (and LLMs) love