0x02. 處理視窗關閉

limit007發表於2019-08-28

上一篇已經使用 sdl2 顯示了一個視窗, 我們當時的做法是讓執行緒休眠 10 秒看了個視窗的樣子, 現在我們想讓這個視窗通過我們主動觸發事件來關閉, 其他正常情況下一直執行.

讓視窗一直顯示

讓視窗一直顯示很好辦, 在原先程式碼基礎上, 通過一個死迴圈就能解決.

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 進行模式匹配來處理資料的多種情況.
先這樣吧.

相關文章