第9课 - 鼠标事件


更新时间:2012年7月5日


是时候学习处理鼠标事件了。本节课将教你处理各种不同类型的鼠标事件并实现一个简单的按钮。
  1. //按钮类
  2. class Button
  3. {
  4. private:
  5. //按钮的属性
  6. SDL_Rect box;
  7. //精灵图中显示出来的子图
  8. SDL_Rect* clip;
  9. public:
  10. //初始化变量
  11. Button( int x, int y, int w, int h );
  12. //处理事件并设置按钮的精灵图子图
  13. void handle_events();
  14. //将按钮显示在窗口中
  15. void show();
  16. };
这是我们的Button类,我们将与它进行交互。

我们用一个矩形来定义按钮的位置和大小。我们还有一个指针,它指向了按钮所使用的精灵图子图。

然后我们有一个构造函数,它依据传入的参数设置来按钮的属性。然后是我们的handle_events() 函数,它用于处理鼠标移动事件和鼠标按钮事件。

接下来是show() 函数,它会将按钮显示到窗口中。
  1. void set_clips()
  2. {
  3. //分割精灵图
  4. clips[ CLIP_MOUSEOVER ].x = 0;
  5. clips[ CLIP_MOUSEOVER ].y = 0;
  6. clips[ CLIP_MOUSEOVER ].w = 320;
  7. clips[ CLIP_MOUSEOVER ].h = 240;
  8.  
  9. clips[ CLIP_MOUSEOUT ].x = 320;
  10. clips[ CLIP_MOUSEOUT ].y = 0;
  11. clips[ CLIP_MOUSEOUT ].w = 320;
  12. clips[ CLIP_MOUSEOUT ].h = 240;
  13.  
  14. clips[ CLIP_MOUSEDOWN ].x = 0;
  15. clips[ CLIP_MOUSEDOWN ].y = 240;
  16. clips[ CLIP_MOUSEDOWN ].w = 320;
  17. clips[ CLIP_MOUSEDOWN ].h = 240;
  18. clips[ CLIP_MOUSEUP ].x = 320;
  19. clips[ CLIP_MOUSEUP ].y = 240;
  20. clips[ CLIP_MOUSEUP ].w = 320;
  21. clips[ CLIP_MOUSEUP ].h = 240;
  22. }
这是我们从精灵图中分割子图的函数。
你可以看到,我们有一张显示不同类型鼠标事件的精灵图。所以我们有一个包含4个SDL_Rect类型数据的数组,它被用来从精灵图中分割出每个按钮子图。每个按钮子图都有一个与之相关的常量。
  1. Button::Button( int x, int y, int w, int h )
  2. {
  3. //设置按钮的属性
  4. box.x = x;
  5. box.y = y;
  6. box.w = w;
  7. box.h = h;
  8. //设置默认的子图
  9. clip = &clips[ CLIP_MOUSEOUT ];
  10. }
Button类的构造函数十分浅显易懂,它设置了按钮的X/Y轴上的偏移量以及其宽高。
译者注:offset直译是“偏移量”,指的从坐标原点(注意是左上角)沿着某一坐标轴平移到某一点时所经过的距离,平移方向决定了+/-号,故含义上同“坐标”。因此本文中的“偏移量”与“坐标”指的是一个概念,并且为了保证语句通顺,“偏移量”与“坐标”两种翻译可能都会用到。

并且它将精灵图中的某一个子图设为了默认子图。
  1. void Button::handle_events()
  2. {
  3. //鼠标坐标
  4. int x = 0, y = 0;
  5. //如果鼠标发生了移动
  6. if( event.type == SDL_MOUSEMOTION )
  7. {
  8. //获得鼠标坐标
  9. x = event.motion.x;
  10. y = event.motion.y;
  11. //如果鼠标在按钮上方
  12. if( ( x > box.x ) && ( x < box.x + box.w ) && ( y > box.y ) && ( y < box.y + box.h ) )
  13. {
  14. //设置按钮子图
  15. clip = &clips[ CLIP_MOUSEOVER ];
  16. }
  17. //如果不是
  18. else
  19. {
  20. //设置按钮子图
  21. clip = &clips[ CLIP_MOUSEOUT ];
  22. }
  23. }
在事件处理函数中,我们首先要做的是检查鼠标是否发生了移动。鼠标移动后,会产生一个SDL_MOUSEMOTION 事件。

如果鼠标发生了移动,我们从事件结构体中获取鼠标的偏移量,然后检查鼠标是否在按钮的上方。如果鼠标在按钮的上方,我们将按钮的子图设为“Mouse Over”子图,否则设为“Mouse Out”子图。
  1. //如果一个鼠标按钮被按下了
  2. if( event.type == SDL_MOUSEBUTTONDOWN )
  3. {
  4. //如果鼠标左键被按下了
  5. if( event.button.button == SDL_BUTTON_LEFT )
  6. {
  7. //获取鼠标坐标
  8. x = event.button.x;
  9. y = event.button.y;
  10. //如果鼠标在按钮上方
  11. if( ( x > box.x ) && ( x < box.x + box.w ) && ( y > box.y ) && ( y < box.y + box.h ) )
  12. {
  13. //设置按钮子图
  14. clip = &clips[ CLIP_MOUSEDOWN ];
  15. }
  16. }
  17. }
然后我们检查是否有鼠标按钮被按下了。如果一个鼠标按钮被按下,它会产生一个SDL_MOUSEBUTTONDOWN事件。

因为我们想让按钮只与鼠标左键交互,所以我们检查一下鼠标左键是否被按下。

然后我们再检查一下是否是当鼠标位于按钮上方时,发生了左键单击事件。如果是的,将按钮子图设为“Mouse Down”子图。
  1. //如果一个鼠标按钮被释放了
  2. if( event.type == SDL_MOUSEBUTTONUP )
  3. {
  4. //如果鼠标左键被释放了
  5. if( event.button.button == SDL_BUTTON_LEFT )
  6. {
  7. //获取鼠标坐标
  8. x = event.button.x;
  9. y = event.button.y;
  10. //如果鼠标在按钮上方
  11. if( ( x > box.x ) && ( x < box.x + box.w ) && ( y > box.y ) && ( y < box.y + box.h ) )
  12. {
  13. //设置按钮子图
  14. clip = &clips[ CLIP_MOUSEUP ];
  15. }
  16. }
  17. }
  18. }
然后我们检查是否有鼠标按钮被释放了。如果一个鼠标按钮被释放,它会产生一个SDL_MOUSEBUTTONUP 事件。

在这个程序里,我们通过事件结构体来获取鼠标的坐标。实际上,通过SDL_GetMouseState()这个函数来获取鼠标坐标会更高效,但是...呃...我懒得回去改代码了。
  1. void Button::show()
  2. {
  3. //显示按钮
  4. apply_surface( box.x, box.y, buttonSheet, screen, clip );
  5. }
接下来,我们让按钮的子图显示在窗口中。
  1. //分割精灵图
  2. set_clips();
  3. //新建按钮
  4. Button myButton( 170, 120, 320, 240 );
在main()函数的顶部,我们在做完初始化和加载后,要先分割精灵图并新建我们的按钮。
  1. //当用户还没有退出
  2. while( quit == false )
  3. {
  4. //如果有事件需要处理
  5. if( SDL_PollEvent( &event ) )
  6. {
  7. //处理鼠标按钮事件
  8. myButton.handle_events();
  9. //如果用户点击了窗口右上角的关闭按钮
  10. if( event.type == SDL_QUIT )
  11. {
  12. //退出程序
  13. quit = true;
  14. }
  15. }
  16.  
  17. //使用白色填充整个窗口
  18. SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) );
  19. //显示按钮
  20. myButton.show();
  21. //更新窗口
  22. if( SDL_Flip( screen ) == -1 )
  23. {
  24. return 1;
  25. }
  26. }
这里展示了Button类在主循环中的具体用法。

一开始我们要处理事件。你可以看出来,我们使用了Button类的事件处理函数并且我们也检查用户是否需要退出。

一般情况下,我们使用while循环来处理事件,但在本节课中(以及前面一节课中),我们都用了“if”。这么做会使得每一帧中仅有一个事件被处理,于是可以更容易地观察到单独的事件。但在绝大多数真实的应用中,你会使用“while”循环,因为你需要在每一帧中处理所有新的事件。

事件处理完毕后,我们用填充白色的方法清除窗口中旧的图像。然后我们让按钮显示出来并更新窗口。

下面主循环会继续运行,所以我们可以一帧一帧地渲染,直到用户请求退出。

对于你们当中使用较快电脑的人来说,可能看不到“Mouse Up”子图。这是因为这程序跑得太快了,以至于这个子图仅仅显示了极短的时间。幸运的是,我们下面会有一系列关于计时和调节帧率的教程。如果你将程序的运行速度调节到20帧每秒以下,你至少应该可以注意到这个子图。

本课所用的多媒体文件和源代码可从此处下载

下一课:按键状态