Welcome!

Nannou is an open-source, creative-coding toolkit for Rust.

The aim of this guide is to help you find the information you are looking for. Whether you are new to Nannou and are looking for a place to start, or you are an experienced Nannou user looking for more advanced tutorials, this book should have something for you!

As excited as we are about developing tools for creative coding, we are equally excited about fostering a warm, welcoming and inclusive community. Please make yourself familiar with our Code of Conduct and feel free to join us on the Nannou Slack!

Why Nannou?

Here you can read about the motivations and philosophy behind Nannou. Why start Nannou? What drives forward progress?

Getting Started

Is this your first time using Nannou or Rust? This is the chapter for you. This chapter covers everything from installing Rust right through to starting your own Nannou project.

Tutorials

A suite of tutorials demonstrating how to do different things with Nannou. For example, "How do I output sounds?", "How do I draw shapes?", "How can I connect to my laser?"

Developer Reference

Learn more about the design philosophy behind Nannou, how the project is architected and how you can contribute.

API Reference

If you are looking for the source code reference, check out docs.rs/nannou. Here you can find documentation about the API generated from the code itself.

Showcases

See what's possible with Nannou! A collection of projects made with Nannou.

Why Nannou?

nannou is a collection of code aimed at making it easy for artists to express themselves with simple, fast, reliable, portable code. Whether working on a 12-month installation or a 5 minute sketch, this framework aims to give artists easy access to the tools they need.

The project was started out of a desire for a creative coding framework inspired by Processing, OpenFrameworks and Cinder, but for Rust. Named after this.

Goals

Nannou aims to provide easy, cross-platform access to the things that artists need:

  • Windowing & Events via winit.
  • Audio via CPAL. Input and output streams. Duplex are not yet supported.
  • Video input, playback and processing (would love suggestions and ideas).
  • GUI via conrod. May switch to a custom nannou solution in the future.
  • Geometry with functions and iterators for producing vertices and indices:
    • 1D - Scalar, Range.
    • 2D - Rect, Line, Ellipse, Polygon, Polyline, Quad, Tri.
    • 3D - Cuboid.
    • 3D TODO - Ellipsoid, Cube, Prisms, Pyramids, *Hedrons, etc.
    • Vertex & index iterators.
    • Graph for composing geometry.
  • Graphics via Vulkan (via vulkano):
    • Draw API. E.g. draw.ellipse().w_h(20.0, 20.0).color(RED).
    • Mesh API.
    • Image API (currently only supported via GUI).
    • Framebuffer object API.
  • Protocols:
    • OSC - Open Sound Control.
    • CITP - Controller Interface Transport Protocol (network implementation is in progress).
    • Ether-Dream Laser DAC protocol and network implementation.
    • DMX via sACN - commonly used for lighting and effects.
    • Serial - commonly used for interfacing with LEDs and other hardware.
    • MIDI - Musical Instrument Digital Interface.
    • UDP via std.
    • TCP streams and listeners via std.
  • Device & I/O stream APIs:
  • Graphical Node Graph via gantz.
  • GUI Editor.

Nannou aims to use only pure-rust libraries. As a new user you should require nothing more than cargo build to get going. Falling back to C-bindings will be considered as a temporary solution in the case that there are no Rust alternatives yet in development. We prefer to drive forward development of less mature rust-alternatives than depend on bindings to C code. This should make it easier for nannou users to become nannou contributors as they do not have to learn a second language in order to contribute upstream.

Nannou will not contain unsafe code with the exception of bindings to operating systems or hardware APIs if necessary.

Nannou wishes to remove the need to decide between lots of different backends that provide access to the same hardware. Instead, we want to focus on a specific set of backends and make sure that they work well.

Why Rust?

Rust is a language that is both highly expressive and blazingly fast. Here are some of the reasons why we choose to use it:

  • Super fast, as in C and C++ fast.
  • A standard package manager that makes it very easy to handle dependencies and share your own projects in seconds.
  • Highly portable. Easily build for MacOS, Linux, Windows, Android, iOS and so many others.
  • No header files (and no weird linking errors).
  • Sum Types and Pattern Matching (and no NULL).
  • Local type inference. Only write types where it matters, no need to repeat yourself.
  • A more modern, ƒunctional and expressive style.
  • Memory safe and data-race-free! Get your ideas down without the fear of creating pointer spaghetti or segfault time-sinks.
  • Immutability by default. Easily distinguish between variables that can change and those that can't at a glance.
  • Module system resulting in very clean and concise name spaces.
  • One of the kindest internet communities we've come across (please visit mozilla's #rust or /r/rust if you're starting out and need any pointers)

Why the Apache/MIT dual licensing?

For the most part, nannou is trying to maintain as much flexibility and compatibility with the licensing of Rust itself, which is also dual licensed.

The Apache 2.0 and MIT license are very similar, but have a few key differences. Using the Apache 2.0 license for contributions triggers the Apache 2.0 patent grant. This grant is designed to protect against leveraging the patent law system to bypass (some) terms of the license. If the contribution is under the Apache 2.0 license, the contributor assures that they will not claim a violation of (their own) patents. If someone makes a work based on Apache 2.0 licensed code, they in turn also vow to not sue their users (for patent infringement). The MIT license provides compatibility with a lot of other FLOSS licenses.

Further reading:

Getting Started

If you are new to Nannou or Rust, you are in the right place!

In this section, we will:

  1. Install the Rust programming language.
  2. Check for platform-specific requirements.
  3. Setup our code editor for working with Rust.
  4. Run some Nannou examples.
  5. Make our own, new Nannou project!

Let's get started.

Platform-specific Setup

Before we get started, let's make sure we have all the necessary ingredients for installing Rust and building nannou projects.

Depending on what OS you are running, you might require an extra step or two.

  • All Platforms

    For now, nannou requires that you have both python and cmake installed. These are required by a tool called shaderc, a part of nannou's graphics stack. The role of this tool is to compile GLSL shaders to SPIR-V so that we may run them using the system's Vulkan implementation. There are a few attempts at pure-rust alternatives to this tool in the works and we hope to switch to one of these in the near future to avoid the need for these extra dependencies.

  • macOS: Ensure that you have xcode-tools installed:

    xcode-select --install
    

    In case it throws an error couldn't find required command: "cmake", you can install cmake by brew:

      brew install cmake
    

    In order to add support for Vulkan (the graphics backend used by nannou) to macOS, nannou will prompt you and attempt to automatically download and install the MoltenVK SDK. MoltenVK is a driver-level implementation of the Vulkan graphics and compute API, that runs on Apple's Metal graphics and compute framework on both iOS and macOS. If you wish to update your MoltenVK SDK version, simply remove the currently installed SDK (this should be at ~/.vulkan_sdk) and nannou will prompt you about downloading and installing the next version the next time you attempt to build a nannou project.

  • Windows: Install the ninja tool.

    This tool is another required by the shaderc tool as mentioned under the "All Platforms" section above.

    1. Download the latest release of ninja from the ninja releases page.
    2. Place the ninja.exe file somewhere you are happy for it to stay.
    3. Add the directory containing ninja.exe to your Path environment variable if it is not already included.
    4. Donwload cmake
    5. Download and install Python
  • Linux: ensure you have the following system packages installed:

    • Basic dev packages

      First make sure our basic dev packages are installed. curl will be required by rustup the rust toolchain manager. build-essential will be required by rustc the rust compiler for linking. cmake and python are necessary for nannou's shaderc dependency to build, as mentioned in the "All Platforms" section above. pkg-configwill be used to retrieve information about alsa during the building process.

      For Debian/Ubuntu users:

      sudo apt-get install curl build-essential python cmake pkg-config
      
    • alsa dev package

      For Fedora users:

      sudo dnf install alsa-lib-devel
      

      For Debian/Ubuntu users:

      sudo apt-get install libasound2-dev
      

      For Arch users:

      sudo pacman -S alsa-lib
      
    • curl lib dev package

      Nannou depends on the curl-sys crate. Some Linux distributions use LibreSSL instead of OpenSSL (such as AlpineLinux, Voidlinux, possibly others if manually installed).

    • vulkan

      Installing Vulkan support on Linux is generally quite easy using your distro's package manager. That said, there may be different driver options to consider depending on your graphics card and tolerance for proprietary software. The following are rough guidelines on how to get going quickly, however if you are at all concerned with finding the approach that suits you best we recommend searching for vulkan driver installation for your graphics card on your distro.

      For Fedora with AMD graphic cards:

      sudo dnf install vulkan vulkan-info
      

      For Fedora with NVIDIA graphic cards: Add the proprietary drivers

      sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
      

      and run

      sudo dnf install xorg-x11-drv-nvidia akmod-nvidia vulkan-tools
      

      For Debian with AMD or Intel graphic cards:

      sudo apt-get install libvulkan1 mesa-vulkan-drivers vulkan-utils
      

      For Debian with NVIDIA graphic cards:

      sudo apt-get install vulkan-utils
      

      For Ubuntu users with AMD or Intel graphic cards: Add a PPA for the latest drivers

      sudo add-apt-repository ppa:oibaf/graphics-drivers
      sudo apt-get update
      sudo apt-get upgrade
      

      and run

      sudo apt-get install libvulkan1 mesa-vulkan-drivers vulkan-utils
      

      For Ubuntu users with NVIDIA graphic cards: Add a PPA for the latest drivers

      sudo add-apt-repository ppa:graphics-drivers/ppa
      sudo apt-get update
      sudo apt-get upgrade
      

      and run

      sudo apt-get install nvidia-graphics-drivers-396 nvidia-settings vulkan vulkan-utils
      

      For Arch with AMD graphic cards:

      sudo pacman -S vulkan-radeon lib32-vulkan-radeon
      

      For Arch with Intel graphics card:

      sudo pacman -S vulkan-intel
      

      For Arch with NVIDIA graphic cards:

      sudo pacman -S nvidia lib32-nvidia-utils
      

OK, we should now be ready to install Rust!

Installing Rust

Nannou is a library written for the Rust programming language. Thus, the first step is to install Rust!

To install Rust on Windows, download and run the installer from here. If you're on macOS or Linux, open up your terminal, copy the text below, paste it into your terminal and hit enter.

curl https://sh.rustup.rs -sSf | sh

Now Rust is installed!

Next we will install some tools that help IDEs do fancy things like auto-completion and go-to-definition.

rustup component add rust-src rustfmt-preview rust-analysis

Please see this link if you would like more information on the Rust installation process.

Editor Setup

While most popular development environments support Rust, support for certain features like auto-completion and go-to-definition is better in some than others.

VS Code

For new Rust users we recommend using VS-Code as your editor and IDE for Nannou development. Currently it seems to have the best support for the Rust language including syntax highlighting, auto-complete, code formatting and more. It also comes with an integrated unix terminal and file navigation system. Below are the steps we recommend for getting started with Nannou development using VS-Code.

  1. Download VS-Code for your OS.
  2. In VS code user settings, set "rust-client.channel": "stable".
  3. Install RLS (the Rust Language Server) plugin for VS-Code.
  4. Click on the 'view' menu and select 'integrated terminal'.

Other Environments

Here are links to assist with setting up other popular development environments for supporting Rust.

  1. Sublime Text
  2. Atom
  3. Intellij IDEA
  4. Vim
  5. Emacs
  6. Visual Studio
  7. Eclipse

Running Examples

The easiest way to get familiar with Nannou is to explore the examples. To get the examples we just need to clone the Nannou repository.

git clone https://github.com/nannou-org/nannou

If you do not have git installed you can press the "Clone or download" button at the top of this page and then press "Download .zip".

Now, change the current directory to nannou.

cd nannou

Run the example using cargo.

cargo run --release --example simple_draw

The --release flag means we want to build with optimisations enabled.

If you are compiling nannou for the first time you will see cargo download and build all the necessary dependencies.

cargo

Once the example compiles you should see the following window appear.

simple_draw_HD

To run any of the other examples, replace simple_draw with the name of the desired example.

Create A Project

Whether we are creating an artwork, an app, a quick sketch or an installation, we want to begin by creating a new project. A new nannou project lets us build a nannou application the way that we want to use it.

Eventually, the aim for Nannou is to provide a project generator tool which will allow us to do the following and much more in just a few clicks. For now, we can create a new project with just a few small steps:

  1. Create the Rust project with the name of our project:

    cargo new my-project
    
  2. Change directory to the generated project.

    cd my-project
    
  3. Edit the Cargo.toml file and add the latest version of nannou to the bottom like so:

    [package]
    name = "my_project"
    version = "0.1.0"
    authors = ["mitchmindtree <mitchell.nordine@gmail.com>"]
    edition = "2018"
    
    [dependencies]
    nannou = "0.12"
    

    Note that there is a chance the nannou version above might be out of date. You can check the latest version by typing cargo search nannou in your terminal.

  4. Replace the code in src/main.rs with the following to setup our nannou application.

    # extern crate nannou;
    use nannou::prelude::*;
    
    fn main() {
        nannou::app(model)
            .update(update)
            .simple_window(view)
            .run();
    }
    
    struct Model {}
    
    fn model(_app: &App) -> Model {
        Model {}
    }
    
    fn update(_app: &App, _model: &mut Model, _update: Update) {
    }
    
    fn view(_app: &App, _model: &Model, frame: &Frame){
        frame.clear(PURPLE);
    }
    

    If you are new to Rust or simply do not understand the code above just yet, do not fear! In the first tutorial of the next chapter we will break down this code step-by-step.

  5. Trigger the initial build and check that everything is working nicely by running our app!

    cargo run --release
    

    The first build might take a while, as we must build nannou and all of its dependencies from scratch. The following times that we run our app should be much faster!

    Once the project has finished building, it will begin running and we should be able to see a purple window.

That's it! If everything went as planned, you are now ready to start building your own nannou project. Of course, we probably want our application to be more than just a purple window.

To find out how to add more features to our project like graphics, audio, multiple windows, laser output, projection mapping and much more, let's take a look at the next chapter.

Upgrading nannou

You can upgrade to a new version of nannou by editing your Cargo.toml file to use the new crate. For v0.12 use the line nannou = "0.12". Run

cargo update

inside the nannou directory to upgrade all dependencies.

Building Nannou examples might still fail. This is most likely due to new language features. Nannou 0.12 for example requires rustc 1.35.0. You can upgrade your (local) rust toolchain by executing

rustup update

Tutorials

In the previous chapter we prepared everything needed to start our own Nannou project! In this chapter, we will take a more focused look at all of the different features we can add to our Nannou project.

If you are new to Nannou or Rust we recommend starting with the first tutorial before going on. If you are feeling more confident, feel free to choose your own adventure through the following tutorials depending on what you want to add to your project!

Basics

A suite of tutorials for getting familiar with Rust and the Nannou environment.

Drawing

Working with Nannou's Draw API - a simple approach of coding graphics.

Windowing

Walk-throughs for creating and working with one or more windows.

  • Building a custom window
  • Creating multiple windows
  • Drawing to different windows
  • Fullscreen on startup
  • Automatically positioning windows

GUI

How to create a GUI (Graphical User Interface) for you Nannou project.

  • Creating a UI
  • Exploring available UI widgets
  • Multi-window UI

Audio

A suite of guides for working with audio in Nannou.

  • Setting up audio output
  • Setting up audio input
  • Selecting specific audio devices
  • Playing an audio file
  • Basic audio synthesis
  • Many channel audio streams
  • Feeding audio input to output
  • Visualising audio

Video

Loading, playing and recording video in Nannou.

  • Drawing video
  • Recording a window to video
  • Manipulating video playback

Vulkan

Understanding the lower level that underlies all graphics in Nannou.

  • What is Vulkan?
  • The graphics pipeline
  • Compute shaders
  • Fragment shaders
  • Vertex shaders

Projection Mapping

Getting started with using Nannou for projection mapping.

  • Basic quad-warping.

LASERs

Detecting and outputting to LASER DACs on a network.

  • Connecting to a LASER.
  • Detecting LASER DACs.
  • Tweaking LASER interpolation and optimisations.

OSC

  • Setting up an OSC sender.
  • Setting up an OSC receiver.

DMX

Working with DMX over the network via sACN.

  • Working with the sacn crate.

Serial over USB

Working with Serial data in a cross-platform manner.

  • Reading USB serial data.
  • Writing USB serial data.

If you were unable to find what you were looking for above, or if you have an idea for a tutorial not yet present, please feel free to create an issue or a pull request!

Anatomy of a Nannou App

Tutorial Info

  • Author: tpltnt, mitchmindtree
  • Required Knowledge: Getting Started
  • Reading Time: 10 minutes

Nannou is a framework for creative coding in Rust. A framework can be thought of as a collection of building blocks to help accomplish a goal.

If romance stories were frameworks, then you might have the protagonist, their love interest, some struggles, and a happy ending as the building blocks. All of these need to be fleshed out by the author, but using clichés help to tell a story without having to introduce everyone and everything in excruciating detail. If the author wants to tell a horror story, then the clichés of a romance story aren't very helpful.

In the same way you can use nannou to create programs for artistic expression, but you might find it hard to build an office suite. So let's take a look at the building blocks for creative coding together.

Here's an example of a bare-bones nannou app that opens an empty window:

# extern crate nannou;
#
use nannou::prelude::*;

struct Model {}

fn main() {
    nannou::app(model)
        .event(event)
        .simple_window(view)
        .run();
}

fn model(_app: &App) -> Model {
    Model {}
}

fn event(_app: &App, _model: &mut Model, _event: Event) {
}

fn view(_app: &App, _model: &Model, _frame: &Frame) {
}

We will start from the top!

Import Common Items

# #![allow(unused_imports)] 
# extern crate nannou;
use nannou::prelude::*;
# fn main() {}

This line imports all of the commonly used items from nannou into scope. These include items such as App, Frame, and many more that we will learn about over time. To see the full list of items re-exported by the prelude, see here.

Note: Unlike some other languages, Rust does not automatically include everything from the libraries added to the project. This approach results in very clean namespaces and avoids conflicts between different items from different crates. That said, it also means we need to manually import every item we do want to use into scope. By providing a prelude nannou makes it a little easier to access all of the commonly used items.

Model - Our app state

# #![allow(dead_code)] 
struct Model {}
# fn main() {}

The Model is where we define the state of our application. We can think of the model as the representation of our program at any point in time. Throughout the life of our program, we can update the model as certain events occur such as mouse presses, key presses or timed updates. We can then present the model using some kind of output, e.g. by drawing to the screen or outputting to a laser. We will look at these input and output events in more detail in another tutorial! Our example is as simple as possible, and we have no state to track. Thus our model can stay empty.

Note: A struct describes a set of data. Our struct has no fields and thus is empty. There is no state information to be tracked in this example.

main - Where Rust programs begin and end

# extern crate nannou;
# use nannou::prelude::*;
# struct Model {}
fn main() {
    nannou::app(model)
        .event(event)
        .simple_window(view)
        .run();
}
# fn model(_app: &App) -> Model {
#     Model {}
# }
# fn event(_app: &App, _model: &mut Model, _event: Event) {
# }
# fn view(_app: &App, _model: &Model, _frame: &Frame) {
# }

All Rust programs begin executing at the start of the main function and end when the main function ends. In most nannou programs, the main function is quite small. In short, we build a description of our app and then run it!

# extern crate nannou;
# use nannou::prelude::*;
# struct Model {}
# fn main() {
    nannou::app(model)       // Start building the app and specify our `model`
        .event(event)        // Specify that we want to handle app events with `event`
        .simple_window(view) // Request a simple window to which we'll draw with `view`
        .run();              // Run it!
# }
# fn model(_app: &App) -> Model {
#     Model {}
# }
# fn event(_app: &App, _model: &mut Model, _event: Event) {
# }
# fn view(_app: &App, _model: &Model, _frame: &Frame) {
# }

We will describe what these model, event and view functions do below!

Note: In this app building process we get a hint at the fundamental design archetype of nannou apps. The approach is roughly based on the Model-View-Controller (MVC) pattern, though equally inspired by Functional Reactive Programming (FRP).

In general, these paradigms split a program into:

  • a model describing the internal state
  • a view describing how to present the model and
  • a controller describing how to update the model on certain events.

If you zoom out a bit you can think of the computer as a model, the screen as a view (the audio output could also be thought of as a view), and the keyboard (or mouse) as the controller. A user looks at the view and can change the state of the model using the controller. If a program does not require user input, the controller might use an algorithm based on time or some other application event to modify the model.

model - initialise our Model

# #![allow(dead_code)] 
# extern crate nannou;
# use nannou::prelude::*;
# struct Model {}
fn model(_app: &App) -> Model {
    Model {}
}
# fn main() {}

The model function is run once at the beginning of the nannou app and produces a fresh, new instance of the Model that we declared previously, AKA the app state. This can be thought of as the "setup" stage of our application. Here, we might do things like create some windows, create a GUI, load some images or anything else that we want to happen once at the beginning of our program. We will learn how to do all of these things in future tutorials, but for now we will just return an instance of our empty Model.

Note: To assist with the creation of windows, GUIs, audio streams and other kinds of I/O, access to the App is provided as an input to the function. The App type can be thought of as a helper type that wraps up the finicky details of the application (such as establishing event loops, spawning I/O streams, etc) and provides an easy to use, high-level API on top. Providing access to the App via a function's first argument is a common practice throughout nannou's API.

# #![allow(dead_code)] 
# extern crate nannou;
# use nannou::prelude::*;
# struct Model {}
//                ----- Access to the `App` passed as an input to the function.
//               /
//              v
fn model(_app: &App) -> Model {
    Model {}
}
# fn main() {}

You can learn more about what the App is responsible for and capable of here.

event - updating the Model on app events

# #![allow(dead_code)] 
# extern crate nannou;
# use nannou::prelude::*;
# struct Model {}
fn event(_app: &App, _model: &mut Model, _event: Event) {
}
# fn main() {}

The event function is some code that will run every time some kind of app event occurs. There are many different kinds of app events including mouse and keyboard presses, window resizes, timed updates and many more. Each of these are events during which we may wish to update our Model in some way. For example, we may wish to turn a camera when a mouse is moved, begin drawing a shape when a button is pressed, or step forward an animation on timed updates.

All of these events are described within the Event type. One way to distinguish between which event is currently occurring is to "pattern match" on the event and handle only those events that we care about, ignoring all the others. A simpler approach is to not register an event function while building the app at all, and instead only register more specific functions for those events that we care about.

For example, if instead of handling all events we only want to handle timed updates (an event that by default occurs 60 times per second) we could change our app building code to this:

# #![allow(dead_code)] 
# extern crate nannou;
# use nannou::prelude::*;
# struct Model {}
fn main() {
    nannou::app(model)
        .update(update) // rather than `.event(event)`, now we only subscribe to updates
        .simple_window(view)
        .run();
}
# fn model(_app: &App) -> Model {
#     Model {}
# }
# fn update(_app: &App, _model: &mut Model, _update: Update) {
# }
# fn view(_app: &App, _model: &Model, _frame: &Frame) {
# }

And remove our event function in favour of an update function:

# #![allow(dead_code)] 
# extern crate nannou;
# use nannou::prelude::*;
# struct Model {}
fn update(_app: &App, _model: &mut Model, _update: Update) {
}
# fn main() {}

Now, our new update function will only run each time a timed update occurs.

Note: Nannou provides a whole suite of different events that may be registered while building an app or window in this way. See the all_functions.rs example for a demonstration of most of the different kinds of events that are available.

view - presenting the Model to a window

# #![allow(dead_code)] 
# extern crate nannou;
# use nannou::prelude::*;
# struct Model {}
fn view(_app: &App, _model: &Model, _frame: &Frame) {
}
# fn main() {}

Finally, the view allows us to present the state of the model to a window by drawing to its Frame and returning the frame at the end. Here we can change the background colour, use the Draw API to draw a scene, draw a GUI to the window or even use the low-level Vulkan API to draw to the frame using our own graphics pipeline. All of this will be covered by future tutorials.

Concluding Remarks

Hopefully this has given you a rough idea of how nannou apps work! Do not stress if some of the syntax looks confusing or some of the specifics still seem unclear - we will aim to cover these and more in future tutorials :)

Drawing 2D Shapes

In this tutorial we explore drawing 2D shapes with Nannou. We will cover drawing basic lines, simple polygons (e.g. ellipses, rectangles, etc.), and more complex polygons (where you can create whatever shape you'd like)!

To begin with, we will need a Nannou project file to work with. Copy the following into new file:

use nannou::prelude::*;

fn main() {
    nannou::sketch(view);
}

fn view(app: &App, frame: &Frame) {
    // Prepare to draw.
    let draw = app.draw();

    // Clear the background to purple.
    draw.background().color(PLUM);

    // Draw a blue ellipse with default size and position.
    draw.ellipse().color(STEELBLUE);

    // Write to the window frame.
    draw.to_frame(app, &frame).unwrap();
}

You can also find this file, and other useful examples, in the examples directory of the Nannou source repository.

Drawing Simple Shapes

Let's try running the file! (if you haven't already, you will need to add this file to your Cargo.toml file)

You should a new window with something that looks like this:

A simple circle

Already we are rendering a circle to our canvas. As you may have guessed, the line of code responsible for creating a circle is the call to the ellipse function:

draw.ellipse().color(STEELBLUE);

There are many ways we can alter our circle here. Let's start with changing the size:

draw.ellipse().color(STEELBLUE)
              .w(300.0)
              .h(200.0);

The w function here changes the width of the ellipse to 300 pixels, and the h function changes the height to 200.0 pixels. You should see what we would more colloquially refer to as an ellipse.

We can also change the position of our ellipse with the x_y method:

draw.ellipse().color(STEELBLUE)
              .w(300.0)
              .h(200.0)
              .x_y(200.0, -100.0);

An ellipse

As you can see, we edit our ellipse by chaining together different methods which will change one or more properties of our shape. This is called the Builder pattern. The call to draw.ellipse() returns an object of type Drawing<Ellipse>. In turn, each call to a builder method, such as w(300.0) or x_y(200.0, -100.0), returns the same instance of our shape. By chaining these function calls, we are able to build an ellipse with the attributes we want.

There are several more methods we can use to build our ellipse. You can view the documentation for many of these methods here.

Drawing Rectangles and Quadrilaterals

Drawing a square or rectangle uses the same builder pattern that drawing an ellipse does. In fact, it's similar enough that you can swap out ellipse with rect in the example above to get a working example:

draw.rect().color(STEELBLUE)
           .w(300.0)
           .h(200.0);

You will see an image like this:

A rectangle

In addition to rect, you can also use the quad method, which is for drawing quadrilaterals. This function is similar to rect, but you can also choose to supply your own coordinates for your shape. Try the following:

let point1 = pt2(-10.0, -20.0);
let point2 = pt2(10.0, -30.0);
let point3 = pt2(15.0, 40.0);
let point4 = pt2(-20.0, 35.0);

draw.quad()
    .color(STEELBLUE)
    .w(300.0)
    .h(200.0)
    .points(point1, point2, point3, point4);

You should see the following:

A quadrilateral with custom defined points

The pt2 method above will create a point object that represents a point in XY coordinate space, like a graph or a Cartesian plane. Nannou's coordinate system places (0,0) at the center of the window. This is not like many other graphical creative coding frameworks, which place (0,0) at the upper-leftmost position of the window.

Note that while the Drawing builder objects for different shapes share many of the same builder methods, they do not share all of them. Trying to use the method points on an instance of an Drawing<Ellipse>, for example, will raise an error.

Drawing a Triangle

Additionally, there is one more simple shape method: tri, for drawing triangles. It behaves similarly to quad, where you can supply your own coordinates to decide how the shape looks. Try it out!

A triangle

Drawing Lines

The line function provides a simple way to draw a line:

let start_point = pt2(-30.0, -20.0);
let end_point   = pt2(40.0, 40.0);

draw.line()
    .start(start_point)
    .end(end_point)
    .weight(4.0)
    .color(STEELBLUE);

A simple line

Simply provide a starting point and an ending point, and you have your line.

This is great for simpler drawings, but what if you want to draw something more complicated? A sine wave, for instance.

To draw our sine wave, we will use the polyline function. To use this function, we will supply a collection (or array) of points that represent points on a sine wave. We can generate this array of points using—what else—the sin function!

let points = (0..50).map(|i| {
      let x = (i as f32 - 25.0);          //subtract 25 to center the sine wave
      let point = pt2(x, x.sin()) * 20.0; //scale sine wave by 20.0
      (point, STEELBLUE)
    });
draw.polyline()
    .weight(3.0)
    .colored_points(points);

A sine wave polyline drawing

As you can see, the power of polyline is the ability to draw a series of lines connecting and ordered array of points. With this, you can easily draw a variety of shapes or lines, so long as you can provide or generate the points you need to represent that shape.

For example, a circle:

let radius = 150.0;                   // store the radius of the circle we want to make
let points = (0..=360).map(|i| {      // map over an array of integers from 0 to 360 to represent the degrees in a circle

   let radian = deg_to_rad(i as f32); // convert each degree to radians
   let x = radian.sin() * radius;     // get the sine of the radian to find the x-co-ordinate of
                                      // this point of the circle, and multiply it by the radius
   let y = radian.cos() * radius;     // do the same with cosine to find the y co-ordinate
   (pt2(x,y), STEELBLUE)              // construct and return a point object with a color
});
draw.polyline()                       // create a PathStroke Builder object
    .weight(3.0)
    .colored_points(points);          // tell our PathStroke Builder to draw lines connecting our array of points

A custom circle

A custom drawn circle! ...okay, perhaps this isn't too exciting, given that we already have an easy way of drawing circles with ellipse. But with a simple change to the above code we can generate an outline of a different shape. Let's try using the step_by function, which allows us to choose the interval at which we would like to step through a range or other iterator. So instead of calling (0..=360).map, we will call (0..=360).step_by(45).map:

let points = (0..=360).step_by(45).map(|i| {

The rest of our code will remain unchanged.

Because 45 divides into 360 eight times, our code generated 8 points to represent a regular octagon.

An octagon outline

An octagon!

Try experimenting with different values to pass into step_by and see the different shapes you can create!

As a side note, you may have noticed that we did not use a color function to set the drawing's color this time. Instead, polyline requires that each point be given a color. This means that you can change the color of the polyline point-by-point. Try experimenting with it!

Drawing Custom Polygons

To draw a custom filled-in polygon (and not just an outline), will we use code very similar to our custom circle or octagon code. The main difference is that instead of calling polyline to create a Builder, we call polygon:

let radius = 150.0;
let points = (0..=360).step_by(45).map(|i| {
   let radian = deg_to_rad(i as f32);
   let x = radian.sin() * radius;
   let y = radian.cos() * radius;
   pt2(x,y)
});
draw.polygon()
    .color(STEELBLUE)
    .points(points);

An octagon

Notice how we are again using the color function to set the color of our polygon, similar to the basic polygon functions covered in the beginning of this tutorial.

Concluding Remarks

In this tutorial, we learned about most basic 2D drawing functions with Nannou.

You can view the documentation for the different Drawing objects these return here:

These links provide more information about other functions you can use to change your drawings in a variety of ways.

You have now learned about some of the most commonly used functions for 2D drawing with Nannou. Of course, this is just scratching the surface of ways in which you can generate shapes or polygons with Nannou, but it should serve as a solid starting point in creating your own drawings.

Happy coding!

Developer Reference

TODO: A guide to the architecture of Nannou's internals to help onboard new developers. Describes the layout of modules, how to navigate the reference, the scope of the main nannou crate i.e. the kinds of work that should go inside the main repo and the kinds that might be better off in separate repos, etc.

API Reference

Eventually, we would like to host the API documentation here. For now, you can find it at docs.rs/nannou.

Showcases

TODO: A collection of work made with nannou, perhaps ordered from most recent to the past. Users could do a PR to have their own nannou projects published.

Contributors

This project exists thanks to all the people who contribute.

Backers

Thank you to all our backers! 🙏

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website.

Contributor Covenant Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Conduct

  • We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
  • Please be kind and courteous. There’s no need to be mean or rude.
  • Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
  • Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
  • We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups.
  • Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the Nannou moderation team immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back.
  • Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.

Moderation

These are the policies for upholding our community’s standards of conduct. If you feel that a thread needs moderation, please contact the Nannou moderation team:

  1. Remarks that violate the Nannou standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)
  2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
  3. Moderators will first respond to such remarks with a warning.
  4. If the warning is unheeded, the user will be “kicked,” i.e., kicked out of the communication channel to cool off.
  5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
  6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
  7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, in private. Complaints about bans in-channel are not allowed.
  8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others.

In the Nannou community we strive to go the extra step to look out for each other. Don’t just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they’re off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.

And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could’ve communicated better — remember that it’s your responsibility to make your fellow members of the community comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@nannou.cc. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4 as well as the Node.js Policy on Trolling and the Rust Code of Conduct