//我们的Window类 class Window { private: //窗口是否是窗口化的 bool windowed; //窗口是否工作正常 bool windowOK; public: //构造函数 Window(); //处理窗口事件 void handle_events(); //切换全屏/非全屏 void toggle_fullscreen(); //检查窗口是否发生了错误 bool error(); };
现在我们有这样的一个Window类,它能管理所有有关可缩放窗口的事情。
我们有两个标志变量。其中,"windowed"标志跟踪了窗口是窗口化的还是全屏的,"windowOK"指示了窗口是否可用。
然后我们有一个构造函数,一个处理事件的函数,一个切换窗口化/全屏模式的函数,以及一个错误检测函数。
我们有两个标志变量。其中,"windowed"标志跟踪了窗口是窗口化的还是全屏的,"windowOK"指示了窗口是否可用。
然后我们有一个构造函数,一个处理事件的函数,一个切换窗口化/全屏模式的函数,以及一个错误检测函数。
bool init() { //初始化SDL的所有子系统 if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ) { return false; } //如果所有操作成功 return true; }
这是我们的初始化函数。
因为所有与窗口有关的东西都在一个单独的类中处理,所以这里我们要做的仅仅是初始化SDL。
因为所有与窗口有关的东西都在一个单独的类中处理,所以这里我们要做的仅仅是初始化SDL。
Window::Window() { //设置窗口 screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE | SDL_RESIZABLE ); //如果发生错误 if( screen == NULL ) { windowOK = false; return; } else { windowOK = true; } //设置窗口标题 SDL_WM_SetCaption( "Window Event Test", NULL ); //设置窗口状态标志 windowed = true; }
在Window类的构造函数中,我们创建了一个窗口。因为我们想让它可缩放,所以我们传入了
然后我们检查screen是否为NULL。如果是的话,我们将“windowOK”设为false,这样可以让我们知道窗口出了问题。
如果所有操作成功完成,我们将“windowOK”设为true,并设置窗口标题,然后,由于我们的窗口一开始是窗口化的,我们还要将"windowed"标志设为true。
SDL_RESIZABLE
标志。然后我们检查screen是否为NULL。如果是的话,我们将“windowOK”设为false,这样可以让我们知道窗口出了问题。
如果所有操作成功完成,我们将“windowOK”设为true,并设置窗口标题,然后,由于我们的窗口一开始是窗口化的,我们还要将"windowed"标志设为true。
void Window::toggle_fullscreen() { //如果窗口是窗口化的 if( windowed == true ) { //设置窗口为全屏模式 screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE | SDL_RESIZABLE | SDL_FULLSCREEN ); //如果发生了错误 if( screen == NULL ) { windowOK = false; return; } //设置窗口状态标志 windowed = false; } //如果窗口是全屏的 else if( windowed == false ) { //将窗口窗口化 screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE | SDL_RESIZABLE ); //如果发生了错误 if( screen == NULL ) { windowOK = false; return; } //设置窗口状态标志 windowed = true; } }
在这个程序里,我加入了按“Enter”键切换窗口/全屏的功能,你只能在可缩放的窗口上这样做。这就是切换窗口化/全屏模式的函数。
当“Enter”键被按下时,我们要检查一下窗口是否是窗口化的。如果是窗口化的,我们就调用
当“Enter”键被按下时,我们要检查一下窗口是否是窗口化的。如果是窗口化的,我们就调用
SDL_SetVideoMode()
函数,并传入SDL_FULLSCREEN
标志,来切换到全屏模式,并将“windowed”标志设为false。如果现在已经是全屏模式,我们就以普通的方式调用SDL_SetVideoMode()
来切换回窗口模式,并将“windowed”标志设为true。void Window::handle_events() { //如果窗口有错误 if( windowOK == false ) { return; } //如果窗口被缩放 if( event.type == SDL_VIDEORESIZE ) { //重新设定窗口大小 screen = SDL_SetVideoMode( event.resize.w, event.resize.h, SCREEN_BPP, SDL_SWSURFACE | SDL_RESIZABLE ); //如果出现错误 if( screen == NULL ) { windowOK = false; return; } }
这里我们开始做事件处理,首先要做的是检查窗口是否是可用的。然后我们处理视窗缩放事件。
无论何时,只要窗口被缩放,你都必须使用
无论何时,只要窗口被缩放,你都必须使用
SDL_SetVideoMode()
重新设定窗口。新窗口的尺寸存储在我们的事件结构体中,宽和高分别是event.resize.w
和 event.resize.h
,所以你可以很容易地将窗口设定为新的尺寸。//如果“Enter”键被按下 else if( ( event.type == SDL_KEYDOWN ) && ( event.key.keysym.sym == SDLK_RETURN ) ) { //切换全屏/窗口 toggle_fullscreen(); }
当“Enter”键被按下时,我们切换全屏模式或窗口模式。
//如果窗口焦点发生变化 else if( event.type == SDL_ACTIVEEVENT ) { //如果窗口被最小化或还原 if( event.active.state & SDL_APPACTIVE ) { //如果程序不再是激活状态 if( event.active.gain == 0 ) { SDL_WM_SetCaption( "Window Event Test: Iconified", NULL ); } else { SDL_WM_SetCaption( "Window Event Test", NULL ); } }
这里的
这里我们检查窗口是否由于最小化或还原而变为非激活或激活状态。当窗口被最小化时,
在这个程序里,我们通过改变窗口标题来提示用户发生了这些变化。
SDL_ACTIVEEVENT
又是一种事件。一个SDL_ACTIVEEVENT
事件在程序的窗口、键盘或鼠标变为激活状态或非激活状态时产生。这里我们检查窗口是否由于最小化或还原而变为非激活或激活状态。当窗口被最小化时,
SDL_ActiveEvent
中的“gain”值为0。当窗口被还原时,“gain”的值为1。在这个程序里,我们通过改变窗口标题来提示用户发生了这些变化。
//如果键盘焦点产生了事件 else if( event.active.state & SDL_APPINPUTFOCUS ) { //如果程序失去了键盘焦点 if( event.active.gain == 0 ) { SDL_WM_SetCaption( "Window Event Test: Keyboard focus lost", NULL ); } else { SDL_WM_SetCaption( "Window Event Test", NULL ); } } //如果鼠标焦点产生了事件 else if( event.active.state & SDL_APPMOUSEFOCUS ) { //如果程序失去了鼠标焦点 if( event.active.gain == 0 ) { SDL_WM_SetCaption( "Window Event Test: Mouse Focus Lost", NULL ); } else { SDL_WM_SetCaption( "Window Event Test", NULL ); } } }
这里处理了键盘焦点和鼠标焦点上的事件。
(要检查窗口焦点时,)记得先检查一下
SDL_APPINPUTFOCUS
指的是键盘焦点的变化。SDL_APPMOUSEFOCUS
指的是鼠标焦点的变化。和SDL_APPACTIVE
类似,当失去焦点时,"gain"的值为0,当恢复焦点时,“gain”的值为1。(要检查窗口焦点时,)记得先检查一下
SDL_APPACTIVE
,因为与“state”做“二进制与”计算会产生多种激活事件。译者注:不太清楚作者的意思,原文是:Remember to check for SDL_APPACTIVE first because bitwise AND comparisons with "state" will report other active events.
我有两种理解方式:
我有两种理解方式:
- 检查某种焦点的变化时,需要先用二进制与运算判断是否是我关心的那种焦点。
- 在做多种焦点判断时,需要先判断
SDL_APPACTIVE
,否则后面的二进制与运算会得出其他类型的激活事件。
//如果窗口被修改 else if( event.type == SDL_VIDEOEXPOSE ) { //Update the screen if( SDL_Flip( screen ) == -1 ) { //If there's an error windowOK = false; return; } } }
最终,我们要处理
一个
SDL_VIDEOEXPOSE
事件。一个
SDL_VIDEOEXPOSE
事件是在窗口被程序外部的东西(译者注:如系统的窗口管理器)修改时发生。我们通过更新窗口来处理这个事件。bool Window::error() { return !windowOK; }
这是我们用来检查窗口是否包含错误的函数。
我们将“windowOK”取反,因为当window是OK时,窗口是没有错误的,当window不是OK时,窗口中就有错误了。
我们将“windowOK”取反,因为当window是OK时,窗口是没有错误的,当window不是OK时,窗口中就有错误了。
//退出标志 bool quit = false; //初始化 if( init() == false ) { return 1; } //创建一个窗口 Window myWindow; //如果窗口发生错误 if( myWindow.error() == true ) { return 1; } //加载文件 if( load_files() == false ) { return 1; }
在我们的main函数顶端,我们在初始化完成后创建了一个Window对象。然后,我们检查一下是否有错并继续进行一些常规操作。
//当用户还没有退出时 while( quit == false ) { //当有事件需要处理时 while( SDL_PollEvent( &event ) ) { //处理窗口事件 myWindow.handle_events(); //如果退格键被按下了 if( ( event.type == SDL_KEYDOWN ) && ( event.key.keysym.sym == SDLK_ESCAPE ) ) { //退出程序 quit = true; } //如果用户点击了窗口右上角的关闭按钮 if( event.type == SDL_QUIT ) { //退出程序 quit = true; } } //如果窗口出错 if( myWindow.error() == true ) { return 1; } //用白色填充整个窗口 SDL_FillRect( screen, ≻reen->clip_rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) ); //居中显示图像 apply_surface( ( screen->w - testing->w ) / 2, ( screen->h - testing->h ) / 2, testing, screen ); //更新屏幕 if( SDL_Flip( screen ) == -1 ) { return 1; } }
这是我们的主循环。没有什么需要特别指明的,除了一点,那就是我们需要记得在渲染窗口前检查一下窗口是否有错误,我们不能在一个不可用的窗口中进行渲染。