Rust Programming By Example
上QQ阅读APP看书,第一时间看更新

Drawing

We now have a working window; it'd be nice to draw into it. First, we need to get the window's canvas before starting the main loop:

    let mut canvas = window.into_canvas()
                       .target_texture()
                       .present_vsync()
                       .build()
                       .expect("Couldn't get window's canvas");

A few explanations for the preceding code:

  • into_canvas transforms the window into a canvas so that we can manipulate it more easily
  • target_texture activates texture rendering support
  • present_vsync enables the v-sync (also known as vertical-synchronization) limit
  • build creates the canvas by applying all previously set parameters

Then we'll create a texture that we'll paste onto the window's canvas. First, let's get the texture creator, but before that, add this include at the top of the file:

    use sdl2::render::{Canvas, Texture, TextureCreator};

Now we can get the texture creator:

    let texture_creator: TextureCreator<_> = canvas.texture_creator();

OK! Now we need to create a rectangle. To make things easier to read, we'll create a constant that will be the texture's size (better to put it at the head of the file, just after the imports, for readability reasons):

    const TEXTURE_SIZE: u32 = 32;

Let's create a texture with a 32x32 size:

    let mut square_texture: Texture =
        texture_creator.create_texture_target(None, TEXTURE_SIZE,
TEXTURE_SIZE) .expect("Failed to create a texture");

Good! Now let's color it. First, add this import at the top of the file:

    use sdl2::pixels::Color;

We use the canvas to draw our square texture:

    canvas.with_texture_canvas(&mut square_texture, |texture| {
      texture.set_draw_color(Color::RGB(0, 255, 0));
      texture.clear();
    });

An explanation of the preceding code is as follows:

  • set_draw_color sets the color to be used when drawing occurs. In our case, it's green.
  • clear washes/clears the texture so it'll be filled with green.

Now, we just have to draw this square texture onto our window. In order to make it work, we need it to be drawn into the main loop but right after the event loop.

One thing to note before we continue: when drawing with the SDL2, the (0, 0) coordinates are at the top-left of a window, not at the bottom-left. The same goes for all shapes.

Add this import at the top of your file:

    use sdl2::rect::Rect;

Now let's draw. In order to be able to update the rendering of your window, you need to draw inside the main loop (and after the event loop). So firstly, let's fill our window with red:

    canvas.set_draw_color(Color::RGB(255, 0, 0));
    canvas.clear();

Next, we copy our texture into the window in the top-left corner with a 32x32 size:

    canvas.copy(&square_texture,
            None,
            Rect::new(0, 0, TEXTURE_SIZE, TEXTURE_SIZE))
        .expect("Couldn't copy texture into window");

Finally, we update the window's display:

     canvas.present();

So if we take a look at the full code, we now have the following:

    extern crate sdl2;

    use sdl2::event::Event;
    use sdl2::keyboard::Keycode;
    use sdl2::pixels::Color;
    use sdl2::rect::Rect;
    use sdl2::render::{Texture, TextureCreator};

    use std::thread::sleep;
    use std::time::Duration;

    fn main() {
      let sdl_context = sdl2::init().expect("SDL initialization  
failed"
); let video_subsystem = sdl_context.video().expect("Couldn't get
SDL video subsystem"
); // Parameters are: title, width, height let window = video_subsystem.window("Tetris", 800, 600) .position_centered() // to put it in the middle of the screen .build() // to create the window .expect("Failed to create window"); let mut canvas = window.into_canvas() .target_texture() .present_vsync() // To enable v-sync. .build() .expect("Couldn't get window's canvas"); let texture_creator: TextureCreator<_> =
canvas.texture_creator(); // To make things easier to read, we'll create a constant
which will be the texture's size.
const TEXTURE_SIZE: u32 = 32; // We create a texture with a 32x32 size. let mut square_texture: Texture = texture_creator.create_texture_target(None, TEXTURE_SIZE,
TEXTURE_SIZE) .expect("Failed to create a texture"); // We use the canvas to draw into our square texture. canvas.with_texture_canvas(&mut square_texture, |texture| { // We set the draw color to green. texture.set_draw_color(Color::RGB(0, 255, 0)); // We "clear" our texture so it'll be fulfilled with green. texture.clear(); }).expect("Failed to color a texture"); // First we get the event handler: let mut event_pump = sdl_context.event_pump().expect("Failed
to get SDL event pump"
); // Then we create an infinite loop to loop over events: 'running: loop { for event in event_pump.poll_iter() { match event { // If we receive a 'quit' event or if the user press the
'ESC' key, we quit.
Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { break 'running // We "break" the infinite loop. }, _ => {} } } // We set fulfill our window with red. canvas.set_draw_color(Color::RGB(255, 0, 0)); // We draw it. canvas.clear(); // Copy our texture into the window. canvas.copy(&square_texture, None, // We copy it at the top-left of the window with a 32x32 size. Rect::new(0, 0, TEXTURE_SIZE, TEXTURE_SIZE)) .expect("Couldn't copy texture into window"); // We update window's display. canvas.present(); // We sleep enough to get ~60 fps. If we don't call this,
the program will take
// 100% of a CPU time. sleep(Duration::new(0, 1_000_000_000u32 / 60)); } }

If you run this code, you should have a red window with a small green rectangle at the top-left (just as shown in the following screenshot):

Figure 2.5

Now, what about switching the color of our small rectangle every second? Alright, first thing, we need to create another rectangle. To make things easier, we'll write a small function that will create texture.

As usual, add the following import at the top of your file:

    use sdl2::video::{Window, WindowContext};

For convenience, we'll create a small enum to indicate the color as well:

    #[derive(Clone, Copy)]
    enum TextureColor {
      Green,
      Blue,
    }

To make our lives easier, we'll handle errors outside of the next function, so no need to handle them directly here:

    fn create_texture_rect<'a>(canvas: &mut Canvas<Window>,
       texture_creator: &'a TextureCreator<WindowContext>,
       color: TextureColor, size: u32) -> Option<Texture<'a>> {
       // We'll want to handle failures outside of this function.
      if let Ok(mut square_texture) =
         texture_creator.create_texture_target(None, size, size) {
           canvas.with_texture_canvas(&mut square_texture, |texture| {
             match color {
                TextureColor::Green => 
texture.set_draw_color(Color::RGB(0, 255, 0)), TextureColor::Blue =>
texture.set_draw_color(Color::RGB(0, 0, 255)), } texture.clear(); }).expect("Failed to color a texture"); Some(square_texture) } else { None } }

You'll note that the function returns an Option type, wrapping a texture. Option is an enum containing two variants: Some and None.