0x04. 视图渲染

通过 sdl 渲染视图

Posted on 2019-08-09

之前我们已经用循环让程序持续运行, 使用宏来统一处理事件, 但是 main 函数知道的太多了, 我们目前定一个小目标, 一步一步让入口简化.
照惯例 git checkout -b render-view

使用 trait

trait 目前我们先理解成 Java 的 interface, 就类似于一个接口, 接口定义的函数具体如何实现不管, 但是它对外开放的是一个确定的行为. 但是视图除了渲染, 它的事件要如何处理. 先走一步算一步.

我们先看看之前主函数如何渲染视图的.

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();

通过 SDL 的函数来创建一个渲染器, 这里被命名成 canvas 的东西. 此外, 我们还要考虑如何处理事件. 我们把 canvas 跟事件处理包装起来就好了.

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,
        }
    }
}

定义一个结构体来拿到事件跟渲染器, 然后定一个渲染函数. 还要让主函数晓得视图的动作, 而且之前的事件触发执行都涉及值的变化, 所以传入的 Phi 结构体不仅需要借用, 还要考虑可变.

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

我们来实现一下这个 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
    }
}

在入口使用

现在还是个半成品, 渲染器跟事件给 render 处理了, 但是入口函数还是没有多少精简, 看起来更麻烦了.

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
        }
    }
}

生命周期问题

先不管那么多, 执行一下看看效果, 这里有个坑. 执行后发现跑不起来

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`.

生命周期错误, canvas 好像活得不够久诶. 我们思考一下 canvas 从哪里来的

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

// 注意 Window 的 renderer 函数
impl Window {
    /// Initializes a new `RendererBuilder`; a convenience method that calls `RendererBuilder::new()`.
    pub fn renderer(self) -> RendererBuilder {
        RendererBuilder::new(self)
    }
}

我们可以看到 window 调用 renderer 时把 selfRendererBuilder 了, 等于讲我们的 canvas 拥有 window, 所以我们得保证 canvas 活得够久. 改一下 Phi, 添加一个生命周期的标记.

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,
        }
    }
}

虽然现在还是个半成品, 但是后面还有用途, 我们当下的目的是一步一步让 main 仅仅是作为一个入口存在.


这一节改的东西稍微多那么一捏捏, 有问题看看代码. Coding