简化主函数
我们已经确定要让主函数知道的更少, 在 phi
模块内定义一个 spawn
函数, 入口只要调用这个函数, 就完成视图的处理, 我们要让入口不需要了解到具体生成窗口之类的细节.
pub fn spawn(title: &str) {
let sdl2_context = sdl2::init().unwrap();
let video = sdl2_context.video().unwrap();
let window = video
.window(title, 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: Box<View> = box views::DefaultView;
'running: loop {
context.events.pump();
match current_view.render(&mut context) {
ViewAction::None => context.canvas.present(),
ViewAction::Quit => break 'running
}
}
}
现在主函数直接调用 spawn
就可以了
fn main() {
phi::spawn("Arcade Shooter");
}
如果之前的代码写过的话, 可能发现其实 spawn
相较之前多了一个 box
的关键字, 如果使用的是 nightly
版本的 Rust, 可以在 main.rs
处添加这个特性来启用这个关键字
#![feature(box_syntax)]
要是不想用不稳定的特性, 就把使用 box
的地方改成
Box::new(views::DefaultView)
为什么突然用起了 Box<T>
, 引经据典一下, 根据官方的 Rust Book 的说法, 使用 Box<T>
的场景通常分
- When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
- When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
- When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type
很明显满足第三个使用场景, 我们只需要关心一个值的特征, 不需要关心它具体的类型, 就跟鸭子类型一样, 走起来是鸭子, 叫起来是鸭子, 它本质上是不是鸭子并不重要, 我们只要口感上是鸭子就好了.
现在只考虑一个值是否 impl View
就够了, 所以这里直接使用 Box<T>
.
现在执行一下可以看到跟之前的效果没什么区别, 目前已经做到让主函数变成甩手掌柜了.
视图之间的切换
我们开发一个 iOS App, 通常都会涉及到多个控制器之前的切换, 现在这个应用, 也来实现一下视图切换的事. 把处理事件的宏添加一下空格键的定义, 之后只要按了空格键就会触发切换视图的事件.
events_macro! {
keyboard: {
key_escape: Escape,
key_up: Up,
key_down: Down,
key_space: Space
},
else: {
quit: Quit { .. }
}
}
这时候不得不再次感慨一下实现了宏的感觉真赞.
然后在 views
的模块内, 之前已经为 DefaultView
实现了 View
这个 trait, 现在再来实现一个 ViewB
, DefaultView
也可以改名成 ViewA
为了好区分. 而且这里可以先写上空格键触发后的控制语句.
impl View for ViewB {
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;
}
if let Some(true) = events.now.key_space {}
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
ViewAction::None
}
}
我们在 loop
这块有使用模式匹配的代码, 使用到了 ViewAction 这个枚举类型, 那就改一下 phi
模块内的 ViewAction
的定义, 添加一个切换视图的类型
pub enum ViewAction {
None,
Quit,
ChangeView(Box<View>),
}
活用各种数据类型可以简化解决很多问题, 现在再改一下两个视图结构体对于 View
的实现, 主要是刚才的空格键事件判断这块
pub struct ViewA;
pub struct ViewB;
impl View for ViewA {
fn render(&mut self, context: &mut Phi) -> ViewAction {
......
if let Some(true) = events.now.key_space {
return ViewAction::ChangeView(box ViewB);
}
......
}
}
impl View for ViewB {
fn render(&mut self, context: &mut Phi) -> ViewAction {
......
if let Some(true) = events.now.key_space {
return ViewAction::ChangeView(box ViewA);
}
......
}
}
然后自然是修改 loop
内的代码
'running: loop {
context.events.pump();
match current_view.render(&mut context) {
ViewAction::None => context.canvas.present(),
ViewAction::Quit => break 'running,
ViewAction::ChangeView(view) => current_view = view,
}
}
现在的效果就是点击空格就会发生视图改变, 为了让自己看得出来, 可以把每个视图的背景色设置成不一样的.
现在已经完成了简化主函数的工作, 还搞定了视图的切换. 之后再处理其他方面的工作.