# What is Declarativas?

### tl;dr

[Declarativas](https://github.com/mrozbarry/declarativas) is a zero-dependency javascript library for declarative HTML 5 canvas **context2d** drawing. If you are familiar with how React renders to the DOM, [declarativas](https://github.com/mrozbarry/declarativas) aims to be that for HTML canvas applications.

## Project Goals

#### Provide all context2d properties and draw operations from the canvas API

The [context2d API](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) has a thorough set of properties and draw functions to make it easy to just draw something on a web page. Wrapping these properties and operations in components gives users familiar with declarative web applications, like [Hyperapp](https://github.com/jorgebucaran/hyperapp), [React](https://reactjs.com), and [Vue](https://vuejs.org/).

#### Components should be easy to build

With a basic set of property and draw operation components, declarativas provides a set of building blocks that can be constructed together to build fantastic looking and functioning components. Components are just functions that return other components, just like in your favourite declarative front-end framework.

#### Make a higher-level API to avoid common pitfalls

One thing that can be hard to track down when using the canvas is the canvas state. It's easy to apply a transform, but forget to reset it for your next render, providing hours or days of canvas debugging. Using some higher level, built-in components, you can easily ensure that the state of the canvas does not bleed into your next render (unless you want it to!).

## What declarativas is not

While declarativas is functional and declarative, it is not **reactive**. All this means is that batteries are not included for state management. While this can seem like an important feature, what this does is make it incredibly easy to integrate into your favourite front-end framework, which probably already includes state management. If you want state management without a separate framework, you could check out [ferp](https://github.com/ferp-js/ferp), a state management library that works similar to [Hyperapp](https://github.com/jorgebucaran/hyperapp) and [Elm](https://elm-lang.org/)'s state management.


# Why Declarativas?

## Comparisons

### Summary

Here I will show a comparison between raw canvas, declarativas without jsx, and with jsx, using a function that will render a rotating 20x20 square centered on 100,100 in the canvas.

### Declarativas Goals

One thing to note is declarativas isn't meant to make canvas programs shorter. While custom components can be made to manage multiple canvas calls, the main goal is to make canvas more approachable to the React generation of javascript developers. Thinking about canvas in terms of components makes things easier to manage, and using a virtual canvas operations (similar to virtual dom), it is far easier to test your canvas application and have predictable output, as well as build logical hierarchies.

### Raw Canvas

```javascript
const ctx = document.querySelector('canvas').getContext('2d');

const draw = (lastTimestamp, angle) => (timestamp) => {
  const delta = lastTimestamp
    ? (timestamp - lastTimestamp) / 1000
    : 0;
  
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  
  ctx.save();
  
  ctx.translate(100, 100);
  ctx.rotate(angle * Math.PI / 180);
  
  ctx.fillStyle = 'blue';
  ctx.fillRect(-10, -10, 20, 20);
  
  ctx.restore();
  
  requestAnimationFrame(draw(timestamp, angle + (10 * delta)));
}

requestAnimationFrame(draw(null, 0));
```

#### Pros

* No extra learning on top of the canvas API
* Shortest program

#### Cons

* No meaningful relationship between calls
  * `save` does not imply a later `restore`
  * A lack of hierarchy makes it non-obvious what mutations to the canvas state require the save and restore
* No community consensus on how to build drawing components
* Arguments to some functions are non-obvious

### Declarativas without JSX

```javascript
import {
  render, createElement as c,
  Stateful, Translate, Rotate, ClearCanvas, Properties, FillRect,
} from 'declarativas';
const { , Rect } = components;

const ctx = document.querySelector('canvas').getContext('2d');

const draw = (lastTimestamp, angle) => (timestamp) => {
  const delta = lastTimestamp
    ? (timestamp - lastTimestamp) / 1000
    : 0;
    
  render(
    ctx,
    c(Stateful, {}, [
      c(ClearCanvas, {}),
      c(Translate, { x: 100, y: 100 }),
      c(Rotate, { angle: angle * Math.PI / 180 }),
      
      c(Properties, { fillStyle: 'blue' }),
      c(FillRect, { x: -10, y: -10, w: 20, h: 20 }),
    ]),
  );
  
  requestAnimationFrame(draw(timestamp, angle + (10 * delta)));
}

requestAnimationFrame(draw(null, 0));
```

#### Pros

* Hierarchy of drawing operations
* Clear distinction of drawing code from logic
* Named parameters
* Declarative components can make the code easier to debug and test

#### Cons

* The `createElement` (aliased as `c`) function can look awkward
* More code
* Adds dependency
* Could feel foreign

### Declarativas with JSX

```javascript
/** @jsx c */
import {
  render, createElement as c,
  Group, ClearCanvas, RevertableState, Translate, Rotate, Properties, FillRect,
} from 'declarativas';

const ctx = document.querySelector('canvas').getContext('2d');

const render = (lastTimestamp, angle) => (timestamp) => {
  const delta = lastTimestamp
    ? (timestamp - lastTimestamp) / 1000
    : 0;
    
  render(
    <Group>
      <ClearCanvas />
      <Stateful>
        <Translate x={100} y={100} />
        <Rotate angle={angle * Math.PI / 180} />
        
        <Properties fillStyle="blue" />
        <FillRect x={-10} y={-10} w={20} h={20} />
      </Stateful>
    </Group>,
    ctx,
  );
  
  requestAnimationFrame(draw(timestamp, angle + (10 * delta)));
}

requestAnimationFrame(draw(null, 0));
```

#### Pros

* Hierarchy of drawing operations
* Clear distinction of drawing code from logic
* Named parameters
* Declarative components can make the code easier to debug and test
* Higher familiarity to the code (from the perspective of React) because of the JSX transform used for operations

#### Cons

* More code
* Adds dependency
* Could feel foreign

## TL;DR

If you want a very lean bare-metal canvas experience, there is nothing distinctly wrong with the javascript API for CanvasContext2d. If you want a more manageable code-base that uses a lot of canvas, I do think declarativas has a well defined use. I personally enjoy the way I can draw with declarativas, and hope you would try it and see the difference.


# Installing Declarativas in Your Application

## Installing

You can use nodejs and npm to grab the latest copy of declarativas to get going in seconds.

#### With npm or yarn

Open up your terminal, and do the usual:

```bash
$ npm install --save declarativas
# OR
$ yarn add declarativas
```

And bring it in your code:

{% code title="index.js" %}

```javascript
import { render, createElement as c, ClearCanvas, Properties, StrokeRect } from 'declarativas';

// Get your canvas from an HTML file.
const canvas = document.querySelector('canvas');

render(
  [
    c(ClearCanvas),
    c(Properties, { strokeStyle: 'black' }),
    c(StrokeRect, {
      x: 100,
      y: 100,
      w: 20,
      h: 20,
    })
  ]),
  canvas.getContext('2d'),
);
```

{% endcode %}

#### With a CDN

{% code title="index.html" %}

```markup
<!doctype html>
<html>
<body>
  <canvas></canvas>
  <!-- CDN Import -->
  <script type="javascript" src="https://unpkg.com/declarativas"></script>
  
  <!-- Code -->
  <script type="javascript">
    const { render, createElement as c, ClearCanvas, Properties, StrokeRect } = window.declarativas;
    const canvas = document.querySelector('canvas');

    render(
      [
        c(ClearCanvas),
        c(Properties, { strokeStyle: 'black' }),
        c(StrokeRect, {
          x: 100,
          y: 100,
          w: 20,
          h: 20,
        })
      ]),
      canvas.getContext('2d'),
    );
    
  </script>
</body>
</html>
```

{% endcode %}


# Declarativas API

Declarativas exposes three simple methods to get you going.

## Methods

### Mutating the Canvas Context 2d Api

```javascript
import { createMutator } from 'declarativas';

createMutator(ctx => ctx.fillRect(1, 1, 10, 10));

createMutator(ctx => {
  ctx.fillStyle = 'green';
});
```

The `createMutator(fn)` method is a way to directly manipulate the canvas state. This is the building block to creating components.

### Building Components

```javascript
/**
 * @jsx createElement
 */
import { createMutator, createElement } from 'declarativas';

const FillText = (props, children, context2d) => {
  return [
    createMutator((ctx) => { ctx.fillStyle = props.color; }),
    createMutator((ctx) => ctx.fillText(props.text, props.x, props.y)),
  ];
}

// And to use the component:

createElement(FillText, { color: 'black', text: 'foo', x: 10, y: 10 });

// OR

<FillText color='black' text='foo' x={10} y={10} />
```

### Render components to the canvas

```javascript
import { render, createElement as c, Properties } from 'declarativas';

render(
  [
    c(Properties, { fillStyle: 'black' }),
    c(FillText, { color: 'black', text: 'Hello world', x: 1, y: 1 }),
  ],
  document.querySelector('canvas').getContext('2d'),
);
```

Calling the `render` method will draw whatever virtual canvas nodes you have created onto the canvas you specified. If you want to update the canvas, just call render again. There is no extra state/updating mechanism built in, but there is nothing stopping any developer from integrating declarativas into a state management library.

## Built-in Components

* DrawImage
* FillRect/StrokeRect
* Path - uses the beginPath, path elements, fill, and stroke to build a path
  * MoveTo
  * LineTo
  * Rect
  * RoundRect
  * Arc/ArcTo
  * CurveTo - bezier or quadratic curves
  * Ellipse
  * Text
* ClearRect - clear portion of screen
* ClearCanvas - clear entire canvas
* Translate
* Rotate
* Scale
* Transform
* Stateful - wraps drawing in a save/restore for color and transform states
* Property - write a single property to the context2d
* Properties - write multiple properties to the context2d
* ErrorBoundary - catch errors and render an error message
* Group - work-around for JSX so you can have multiple "root" elements


# Vanilla HTML/JS

No fancy framework, just the basics that your browser provides

## Single render drawing

```markup
<!doctype html>
<html>
<body>
<canvas></canvas>
<script type="module">
import { render, c } from 'https://unpkg.com/declarativas@^0.0.1-beta?module=1';

const canvas = document.querySelector('canvas');

render(
  canvas.getContext('2d'),
  [
    c('clearRect', {
      x: 0,
      y: 0,
      width: canvas.width,
      height: canvas.height,
    }),
    c('strokeStyle', { value: '#000' }),
    c('strokeRect', {
      x: 50,
      y: 50,
      width: 100,
      height: 100,
    })
  ]
);
</script>
</body>
</html>
```

## Render loop

```markup
<!doctype html>
<html>
<body>
<canvas width="640" height="480"></canvas>
<script type="module">
import { render, c } from 'https://unpkg.com/declarativas';

const makeApp = (canvas) => {
  const myApp = (previousState) => (timestamp) => {
    // -- state manipulation
    const delta = previousState.lastRender > 0
      ? ((timestamp - previousState.lastRender) / 1000)
      : 0;
    
    const state = {
      ...previousState,
      angle: (previousState.angle + (60 * delta)) % 360,
      lastRender: timestamp,
    };
    
    // -- render
    render(
      canvas.getContext('2d'),
      [
        c('save'),
        c('clearRect', {
          x: 0,
          y: 0,
          width: canvas.width,
          height: canvas.height,
        }),
        c('translate', { x: canvas.width / 2, y: canvas.height / 2 }),
        c('rotate', { value: state.angle * Math.PI / 180 }),
        c('fillStyle', { value: '#f0f' }),
        c('fillRect', {
          x: -50,
          y: -50,
          width: 100,
          height: 100,
        }),
        c('restore'),
      ]
    );
    
    requestAnimationFrame(
      myApp(nextState)
    );
  };
  
  return myApp;
};

const app = makeApp(document.querySelector('canvas'));
requestAnimationFrame(app({
  angle: 0,
  lastRender: 0,
}));

</script>
</body>
</html>
```


# With Ferp

If you don't want to roll your own state management

Ferp effects are perfect for running the declarativas render method.

```markup
<!doctype html>
<html>
<body>
<canvas></canvas>
<script type="module">
import { app, effects } from 'https://unpkg.com/ferp@^2.0.0-beta?module=1';
import { render, c } from 'https://unpkg.com/declarativas@^0.0.1-beta?module=1';

const getDelta = (state, timestamp) => state.lastRender > 0
  ? ((timestamp - state.lastRender) / 1000)
  : 0;
  
const move = (value, change, multiplier) => value + (change * multiplier);

const scheduleFx = (action) => effects.defer((resolve) => {
  requestAnimationFrame((timestamp) => {
    resolve(effects.act(action(timestamp)));
  });
});

const drawFx = (state, nextFx) => effects.thunk(() => {
  render(state.context2d, state.sceneFn(state));
  return nextFx
});

const drawAction = timestamp => state => {
  const nextState = {
    ...state,
    angle: move(state.angle, 30, getDelta(state, timestamp)),
    lastRender: timestamp
  };
  return [
    nextState,
    drawFx(
      nextState,
      scheduleFx(drawAction),
    ),
  ];
};

const sceneFn = state => [
  c('save'),
  c('clearRect', {
    x: 0,
    y: 0,
    width: state.context2d.canvas.width,
    height: state.context2d.canvas.height,
  }),
  c('translate', { x: state.context2d.canvas.width / 2, y: state.context2d.canvas.height / 2 }),
  c('rotate', { value: state.angle * Math.PI / 180 }),
  c('fillStyle', { value: '#f0f' }),
  c('fillRect', {
    x: -50,
    y: -50,
    width: 100,
    height: 100,
  }),
  c('restore'),
];

app({
  init: [
    {
      sceneFn: sceneFn,
      angle: 0,
      lastRender: 0,
      context2d: document.querySelector('canvas').getContext('2d'),
    },
    scheduleFx(drawAction),
  ]
})

</script>
</body>
</html>
```


