Skip to main content

Overview

What is Flowscape

Flowscape is an engine for editor-style 2D products, not an opinionated application framework.
It is also not a domain-specific SDK with fixed product APIs; it provides runtime primitives you compose into your own product architecture.
The core value is control: scene graph, camera, interaction, and rendering layers that you can adapt to your use case.

You can use Flowscape in browser apps (React, Vue, Svelte, or vanilla TypeScript), desktop apps (Electron, Tauri), and mobile products via WebView-based stacks when needed.

How Flowscape works

Flowscape is built around a Scene with 4 layers, each with a clear responsibility:

LayerResponsibilityTypical content
BackgroundVisual foundation behind world contentSolid fill, watermark, brand texture
WorldMain editable coordinate spaceNodes, shapes, text, app objects
OverlayEditor interaction visuals over worldSelection frames, handles, guides
UIHTML/UI modules outside world transformToolbars, rulers UI, contextual controls

Layer diagram

Scene
|- Background (static visual base)
|- World (editable content space)
|- Overlay (selection + transform helpers)
`- UI (interface modules)

Camera, nodes, math, renderers

  • Camera controls pan/zoom and world-to-screen mapping.
  • Nodes are the scene entities you add, transform, and render.
  • Math layer keeps geometry and transforms deterministic for editor behavior.
  • Renderers are layer-specific adapters, so your core scene logic is not tied to one backend.

Because rendering is abstracted per layer, you can evolve from canvas-based rendering toward WebGL/WebGPU style implementations without rewriting the whole editor architecture.

Minimal world-layer example

import {
Scene,
LayerWorld,
NodeRect,
RendererLayerWorldCanvas,
CanvasRendererHost,
LayerWorldInputController,
} from '@flowscape-ui/core-sdk';

export function mountWorldOnly(container: HTMLDivElement) {
const scene = new Scene(container.clientWidth, container.clientHeight);
const layerWorld = new LayerWorld();
scene.addLayer(layerWorld);

const worldRenderer = new RendererLayerWorldCanvas();
scene.bindLayerRenderer(layerWorld, worldRenderer);

const host = new CanvasRendererHost(container, -1);
scene.addHost(host);

const worldInputController = new LayerWorldInputController();
scene.inputManager.add(layerWorld, worldInputController, {
stage: host.getRenderNode(),
world: layerWorld,
options: {
enabled: true,
panMode: 'right',
zoomEnabled: true,
zoomFactor: 1.08,
preventWheelDefault: true,
keyboardPanSpeed: 900,
keyboardPanShiftMultiplier: 1.5,
},
emitChange: () => scene.invalidate(),
});

const node = new NodeRect(1);
node.setSize(220, 140);
node.setPosition(0, 0);
layerWorld.addNode(node);

scene.invalidate();

const resizeObserver = new ResizeObserver(() => {
scene.setSize(container.clientWidth, container.clientHeight);
scene.invalidate();
});
resizeObserver.observe(container);

return () => {
resizeObserver.disconnect();
scene.inputManager.remove(worldInputController.id);
scene.removeHost(-1);
};
}

Live result

The example below uses the same Scene API and renders a world-only setup directly inside the docs page: