devotee 0.2.0-beta.13

Visualization engine
Documentation
# devotee

Simplistic visualization project.

## Using devotee

### Creating an app

Devotee app is represented by the `Root` implementation and a `Backend` system.

#### Root implementation

`Root` implementor must choose desired input system, pixel data converter, and render surface.
Also, it has to implement `update`, `render` and `converter` methods.

Minimalist `Root` implementation may look like this:

```rust
struct Minimal;

impl Root for Minimal {
    type Input = NoInput;
    type Converter = BlackWhiteConverter;
    type RenderSurface = Canvas<bool>;

    fn update(&mut self, _: AppContext<Self::Input>) {}

    fn render(&self, _: &mut Self::RenderSurface) {}

    fn converter(&self) -> Self::Converter {
        BlackWhiteConverter
    }
}
```

This `Root` implementation:

- Uses `NoInput` as input system;
- Relies on the `BlackWhiteConverter` (implementation will be discussed later) to convert data of the `RenderSurface`;
- Uses `Canvas` with `bool` pixels as a `RenderSurface`;
- Does nothing during `update`;
- Draws nothing during `render`;
- Returns `BlackWhiteConverter` instance for data conversion;

The sample `BlackWhiteConverter` is implemented as:

```rust
struct BlackWhiteConverter;

impl Converter for BlackWhiteConverter {
    type Data = bool;

    fn convert(&self, _x: usize, _y: usize, data: Self::Data) -> u32 {
        if data {0xffffffff} else {0xff000000}
    }
}
```

It ignores `x` and `y` coordinates of the pixel and returns either pure white or pure black depending on the `data` value.

#### Backend usage

So, with the `Root` being implemented it is time to launch it using some backend.

For this example we will rely on the [Softbuffer](https://crates.io/crates/softbuffer)-based backend [implementation](https://crates.io/crates/devotee-backend-softbuffer).

```rust
fn main() -> Result<(), Error> {
    let backend = SoftBackend::try_new("minimal")?;
    backend.run(
        App::new(Minimal),
        SoftMiddleware::new(Canvas::with_resolution(false, 128, 128), NoInput),
        Duration::from_secs_f32(1.0 / 60.0),
    )
}
```

### Updating app state

Consider `Extended` implementation of `Root`.

```rust
struct Extended {
    counter: f32,
}
```

Let it use `Keyboard` as input.
It shuts down on the `Escape` button being pressed.
Also, it counts passed simulation time in `counter`.

So, first part of its implementation looks like this:

```rust
impl Root for Extended {
    type Input = Keyboard;
    type Converter = BlackWhiteConverter;
    type RenderSurface = Canvas<bool>;

    fn update(&mut self, mut context: devotee::app::AppContext<Self::Input>) {
        if context.input().just_pressed(KeyCode::Escape) {
            context.shutdown();
        }

        self.counter += context.delta().as_secs_f32();
    }

    // ...
}
```

During render it cleans render surface, calculates the surface center and draws two filled circles using `painter`.
`Painter` instance accepts functions as arguments instead of pure colors.
The function decides what to do with the pixel passed given its coordinates.
`paint` is a predefined function to override any original value.

Note that there are two implementations of `painter`: for `i32` coordinates and (subpixel one) for `f32` coordinates.

```rust
    //. ..
    fn render(&self, surface: &mut Self::RenderSurface) {
        surface.clear(false);
        let center = surface.dimensions().map(|a| a as f32) / 2.0;

        let mut painter = surface.painter();
        let radius = 48.0 + 16.0 * self.counter.sin();

        painter.circle_f(center, radius, paint(true));
        painter.circle_f(center, radius / 2.0, |x, y, _| (x + y) % 2 == 0)
    }
    // ...
```

## Examples

There are some examples in the `examples` folder.

## License

`devotee` is licensed under the `MIT` license.