0x02. 处理窗口关闭

宏通用处理事件

Posted on 2019-08-06

上一篇已经使用 sdl2 显示了一个窗口, 我们当时的做法是让线程休眠 10 秒看了个窗口的样子, 现在我们想让这个窗口通过我们主动触发事件来关闭, 其他正常情况下一直运行.

开始之前先创建个分支, git checkout -b events

让窗口一直显示

让窗口一直显示很好办, 在原先代码基础上, 通过一个死循环就能解决.

use sdl2::pixels::Color;

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 mut canvas = window.renderer().accelerated().build().unwrap();
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    'running: loop {}
}

Rust 有一个 loop 的循环方式, 'running 可以不用理会只是个生命周期标记. 现在这个程序所处的状态算不上一个正常的状态, 之后要让程序可以受用户控制关闭.

让窗口可以关闭

rust-sdl2 的事件控制方法在其 github 仓库 examples 中有, 就这个样子, 通过调用 sdl2_contextevent_pump 函数获取 sdl2 的事件集, 再在循环中遍历通过事件集的 poll_iter 函数获取到的轮询迭代器

let mut event_pump = sdl2_context.event_pump().unwrap();
'running: loop {
    for event in event_pump.poll_iter() {
        match event {
            Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                break 'running
            },
            _ => {}
        }
    }
}

我们要处理的事件是程序 Quit 事件和按了键盘的 escape 的事件, 现在来创建个文件, 就叫 events.rs, 放到 main.rs 同级目录下

use sdl2::EventPump;

pub struct Events {
    pump: EventPump,
    pub quit: bool,
    pub key_escape: bool,
}

impl Events {
    pub fn new(pump: EventPump) -> Self {
        Self {
            pump,
            quit: false,
            key_escape: false,
        }
    }
    pub fn pump(&mut self) {
        for event in self.pump.poll_iter() {
            use sdl2::event::Event::*;
            use sdl2::keyboard::Keycode::*;
            match event {
                Quit { .. } => self.quit = true,
                KeyDown {
                    keycode: Some(Escape),
                    ..
                } => self.key_escape = true,
                _ => {}
            }
        }
    }
}

现在可以看到一个 Events 的结构体, Rust 结构体目前先理解成其他语言的 class, 我们可以给一个 class 定义属性, 还可以添加构造方法, 一般的方法, 还可以通过 private, public, protected 这类关键字控制属性的访问权限, Rust 结构体也具备这类特性, 不过有点区别的是, 没有 protected 方面的控制. Rust 结构体没有严格命名的构造方法, 根据惯例是使用 new, 只要自己需要, 也可以使用 create, foo, bar … 之类的函数作为构造方法.
Rust 一个非常好使的语法就是模式匹配, 比 Apple 每年出的新语言模式匹配更容易玩. 功能强大, match 后面接一堆正则表达式都可以, 要啥自行车.

现在结构体定义了 quit, key_escape 这些用来标注状态修改的 bool 类型属性, 而 pump 属性是用来给内部的函数使用的, 不使用 pub 关键字来开放外部使用. 我们看到 pump 函数有个 self 的参数, 这个参数可以理解成代表结构体实例本身, 有了这个参数, 可以在结构体实例调用本函数时, 通过 self 使用实例自身的属性, 由于我们将准备在该函数内修改实例自身的属性值, 所以使用 mut 来达到可变的效果. 至于 & 这个符号, 如果想让实例调用 pump 函数后还能继续使用, 得使用借用的方式传 self.

然后在 main.rs 中使用一下我们的事件处理器

#![feature(uniform_paths)]

use sdl2::pixels::Color;
mod events;
use events::Events;

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 mut canvas = window.renderer().accelerated().build().unwrap();
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let mut event = Events::new(sdl2_context.event_pump().unwrap());

    'running: loop {
        event.pump();
        if event.quit || event.key_escape { break 'running; }
    }
}

#![feature(uniform_paths)] 这一段是用来使用 Rust 的新特性, 因为我想简单点使用我们的 events 模块, 之后就可以直接在 main 函数内使用 Events 结构体了.

后面的逻辑很简单, 每次在循环中调用一下 event.pump 来根据触发的事件修改状态, 判断是否停止循环.


这一篇我们使用一个带标记的 loop 循环让程序持续可用. 通过一个结构体, 使用 impl Foo 的方式给结构体添加函数, 了解到函数可以通过 self 使用实例自身. 我们还能使用 match 进行模式匹配来处理数据的多种情况. 先这样吧.