almost 4 years ago

[Table of contents]
[First part]

Last time, we set up a workspace for rust and cargo. In this part, we are going to go from nothing to a window with a spinning square in the middle.

Creating the project

In a terminal, type the following command:

cargo new --bin piston-tutorial

This creates the folder we are going to work with, as well as an initial configuration for cargo.

Inside the folder, you will find it has created a file, Cargo.toml and a directory, src. Inside src you will find the main.rs file, with the basic rust "Hello World" program inside it.

Cargo.toml is the configuration file of our project. Open it up - you will find something similar to the following inside:

[package]
name = "piston-tutorial"
version = "0.1.0"
authors = ["StelarCF <StelarCF@gmail.com>"]

As you can tell, this section describes the basic infromation of our package - its name, the version it is on and who wrote it (the authors part will be different for you, of course).

Add the following lines at the end of the file:

[dependencies]
piston_window = "0.24.0"

This tells Cargo that our project will depend on piston_window, a package which serves as a convenience package for a couple of other packages for windowing, input and graphics.

Now open up src/main.rs and paste in the following code over the old one:

extern crate piston_window;

use piston_window::*;

fn main() {
    let window: PistonWindow = WindowSettings::new(
        "piston-tutorial",
        [600, 600]
    )
    .exit_on_esc(true)
    .build()
    .unwrap();
    for e in window {

    }
}

Then build it with cargo build or (on atom) Ctrl+Alt+B (WARNING: On Atom I have had issues with build and linter-rust, as the latter also uses cargo and it results in issues as cargo cannot have multiple instances building the same project at the same time). You should get a warning such as unused variable: 'e' - ignore this for now. Run your program by typing, in a terminal, ./target/debug/piston-tutorial (or double clicking it on windows). You should see something like this:

If the window is "transparent" - don't worry, we will fix that in a second. Otherwise, hurray! We have our first window. Close it by pressing esc.

What's going on?

Most of the code is pretty intuitive - we include the crate piston_window, bring its contents into the global namespace, create a window with the title "piston-tutorial" and 600 by 600 pixels and tell it to close when we press esc, as well as some boilerplate for displaying the window and retrieving it from our WindowSettings struct.

What might be a little bit confusing is the line for e in window - it looks like an event loop, but we are iterating over the window?!

The reality is that it's quite simple - by iterating on the window, we get an event (in e.event) but we also get a copy of the window's state, rather than having issues with window becoming a moved value. It's also technically correct to write for e in window.events(), but working with that has some differences.

The Square

Now we want to draw our square. To do this, we will call, inside the event loop, a member function of PistonWindow called draw_2d. It takes, as its only argument, a lambda function with two arguments - the graphical context and the graphical instance.

e.draw_2d(|c, g| {

First, let's clear the screen in a nice black:

clear([0.0, 0.0, 0.0, 1.0], g);

Now, let's get a transform for the center of the screen and describe the size and colour of the square:

let center = c.transform.trans(300.0, 300.0);
let square = rectangle::square(0.0, 0.0, 100.0);
let red = [1.0, 0.0, 0.0, 1.0];

Finally, we can display the square:

rectangle(red, square, center.trans(-50.0, -50.0), g); // We translate the rectangle slightly so that it's centered; otherwise only the top left corner would be centered

Build and run; you should get something like this:

Hurray! One last thing, let's make it spin. We will now use e.event to get the deltaT and do updates on the update event. Your event loop should now look like this:

let mut rotation: f64 = 0.0;
for e in window {
    match e.event {
        Some(Event::Update(UpdateArgs { dt })) => {
            rotation += 3.0 * dt;
        }
        _ => {
        }
    }
    e.draw_2d(|c, g| {
        clear([0.0, 0.0, 0.0, 1.0], g);
        let center = c.transform.trans(300.0, 300.0);
        let square = rectangle::square(0.0, 0.0, 100.0);
        let red = [1.0, 0.0, 0.0, 1.0];
        rectangle(red, square, center.rot_rad(rotation).trans(-50.0, -50.0), g); // We translate the rectangle slightly so that it's centered; otherwise only the top left corner would be centered

    });
}

And that's it! Running it will have your square spinning around:

You can find the finished source code for this tutorial here.

For the next part, where we will deal with keyboard input, go here

← Part 0: Prerequisites Part 2: Moving Square →