We’ve already used loops to keep the program running and macros to handle events uniformly, but the main function knows too much, so let’s set a small goal to simplify the entry step by step. Git checkout -b render-view

Use traits

At present, the trait is first understood as Java interface, which is similar to an interface. No matter how the function defined by the interface is implemented, it opens to the outside world as a definite behavior. But how does a view handle its events in addition to rendering. One step at a time.

Let’s first look at how the main function rendered the view.

let sdl2_context = sdl2::init().unwrap();
let video = sdl2_context.video().unwrap();
let window = video
    .window("Arcade Shooter".800.600)
    .position_centered()
    .opengl()
    .build()
    .unwrap();
let mut canvas = window.renderer().accelerated().build().unwrap();
canvas.set_draw_color(Color::RGB(0.0.0));
canvas.clear();
canvas.present();
Copy the code

Create a renderer using the SDL function called Canvas. In addition, we have to consider how to handle events. Let’s just wrap the Canvas around event handling.

use sdl2::render::Renderer;
struct Phi {
    pub events: Events,
    pub canvas: Renderer,
}
impl Phi {
    pub fn new(events: Events, canvas: Renderer) -> Phi {
        Phi {
            events,
            canvas,
        }
    }
}
Copy the code

Define a structure to get the event and renderer, and then specify a render function. The main function is also made aware of the view’s actions, and previous event-triggered executions have involved changing values, so the passed Phi structure not only needs to be borrowed, but also considered mutable.

pub enum ViewAction {
    Quit,
    None,}pub trait View {
    fn render(&mut self, context: &mut Phi) -> ViewAction;
}
Copy the code

Let’s implement this trait

pub struct DefaultView;
impl View for DefaultView {
    fn render(&mut self, context: &mut Phi) -> ViewAction {
        let canvas = &mut context.canvas;
        let events = &mut context.events;

        if events.now.quit || events.now.key_escape == Some(true) {
            return ViewAction::Quit;
        }
        canvas.set_draw_color(Color::RGB(0.0.0));
        canvas.clear();
        ViewAction::None}}Copy the code

Use at the entrance

It’s still a work in progress, the renderer and events are handled by render, but the entry function is still less streamlined and looks more cumbersome.

fn main() {
    let sdl2_context = sdl2::init().unwrap();
    let video = sdl2_context.video().unwrap();
    let window = video
        .window("Arcade Shooter".800.600)
        .position_centered()
        .opengl()
        .build()
        .unwrap();
    let canvas = window.renderer().accelerated().build().unwrap();
    let events = Events::new(sdl2_context.event_pump().unwrap());
    let mut context = Phi::new(events, canvas);
    let mut current_view = views::DefaultView;

    'running: loop {
        context.events.pump();
        match current_view.render(&mut context) {
            ViewAction::None => context.canvas.present(),
            ViewAction::Quit => break 'running}}}Copy the code

Life cycle problem

Anyway, let’s just do it and see what happens. There’s a pit. Execute it and find it can’t run

error[E0106]: missing lifetime specifier
  --> src/phi/mod.rs:19:17
   |
19 |     pub canvas: Renderer,
   |                 ^^^^^^^^ expected lifetime parameter

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.
Copy the code

Wrong life cycle, canvas doesn’t seem to live long enough. Let’s think about where does canvas come from

let canvas = window.renderer().accelerated().build().unwrap();
let events = Events::new(sdl2_context.event_pump().unwrap());
let mut context = Phi::new(events, canvas);

// Notice the Window renderer function
impl Window {
    /// Initializes a new `RendererBuilder`; a convenience method that calls `RendererBuilder::new()`.
    pub fn renderer(self) -> RendererBuilder {
        RendererBuilder::new(self)}}Copy the code

We can see that the Window calls the Renderer and gives self to the RendererBuilder, which means that our canvas owns the window, so we have to make sure that canvas lives long enough. Change Phi to add a life cycle marker.

use sdl2::render::Renderer;
pub struct Phi<'window> {
    pub events: Events,
    pub canvas: Renderer<'window>,}impl<'window> Phi<'window> {
    pub fn new(events: Events, canvas: Renderer<'window>) -> Phi {
        Phi {
            events,
            canvas,
        }
    }
}
Copy the code

It’s still a work in progress, but there will be future uses, and our immediate goal is to make Main a mere entrance step by step.


I’m going to change a little bit more in this video, so if you have any questions, look at the code. Coding