SDL2 没有自带控件,今天尝试自己画个按钮。
用图片按钮是最简单的:找几幅图片,代表不同的按钮状态,根据需要显示即可。
这里实现一个windows标准风格、文字的,可以通用,不用带着不同的图片跑了——图片到底大,而且不通用。
首先把那个五子棋程序里的取得纹理的几个函数独立成一个库,方便调用
// GetTexture.h// SDL2 公共函数 - 取得 RGB、文字、图片的纹理#include#include #include #ifndef SDL2_GET_TEXTURE_H#define SDL2_GET_TEXTURE_H// 取得颜色板纹理// 参数:pRenderer = 渲染器;color = 颜色;width, height = 颜色板宽、高// 返回值:纹理指针extern SDL_Texture *GetRGBTexture(SDL_Renderer *pRenderer, int color, int width, int height);// 取得图片文件纹理// 参数:pRenderer = 渲染器;FileName = 图片文件名;bTransparent = 是否透明处理;color = 背景色// 返回值:纹理指针extern SDL_Texture *GetImageTexture(SDL_Renderer *pRenderer, char *FileName, _Bool bTransparent, int color);// 取得字符串纹理// 参数:pRenderer = 渲染器;szString = 字符串内容;pFont = 字体;color = 文字颜色// 返回值:纹理指针extern SDL_Texture *GetTextTexture(SDL_Renderer *pRenderer, char *text, TTF_Font *pFont, int color);#endif
// GetTexture.c// SDL2 公共函数 - 取得 RGB、文字、图片的纹理#include "GetTexture.h"// 取得颜色板纹理// 参数:pRenderer = 渲染器;color = 颜色;width, height = 颜色板宽、高// 返回值:纹理指针SDL_Texture *GetRGBTexture(SDL_Renderer *pRenderer, int color, int width, int height){ SDL_Texture *pTexture; SDL_Surface *pSurface; if((pSurface = SDL_CreateRGBSurface(0, width, height, 32, 0, 0, 0, 0)) == NULL) return NULL; SDL_FillRect(pSurface, NULL, color); pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface); SDL_FreeSurface(pSurface); return pTexture;}// 取得图片文件纹理// 参数:pRenderer = 渲染器;FileName = 图片文件名;bTransparent = 是否透明处理;color = 背景色// 返回值:纹理指针SDL_Texture *GetImageTexture(SDL_Renderer *pRenderer, char *FileName, _Bool bTransparent, int color){ SDL_Texture *pTexture; SDL_Surface *pSurface; if((pSurface = IMG_Load(FileName)) == NULL) return NULL; if(bTransparent) SDL_SetColorKey(pSurface, 1, SDL_MapRGB(pSurface->format, color>>16, (color>>8)&0xFF, color&0xFF)); pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface); SDL_FreeSurface(pSurface); return pTexture;}// 取得字符串纹理// 参数:pRenderer = 渲染器;szString = 字符串内容;pFont = 字体;color = 文字颜色// 返回值:纹理指针SDL_Texture *GetTextTexture(SDL_Renderer *pRenderer, char *text, TTF_Font *pFont, int color){ SDL_Texture *pTexture; SDL_Surface *pSurface; SDL_Color c = {color>>16, (color>>8)&0xFF, color&0xFF}; if((pSurface = TTF_RenderUTF8_Blended(pFont, text, c)) == NULL) return NULL; pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface); SDL_FreeSurface(pSurface); return pTexture;}
在这个基础上画按钮
// button.h// SDL2 自定义部件 - 按钮#include#include #ifndef SDL_WIDGET_BUTTON_H#define SDL_WIDGET_BUTTON_H// 按钮状态typedef enum en_SDL_Button_State{ BTN_STATE_NORMAL, // 正常 BTN_STATE_DOWN, // 按下 BTN_STATE_UP // 弹起} SDL_Button_State;// 按钮结构typedef struct st_SDL_Button{ int id; int x, y, w, h; // 窗口(渲染器)定位 char *text; // 文字 _Bool enable; // 是否可用 SDL_Button_State state; // 状态} SDL_Button;// 画按钮// 参数:pRenderer = 渲染器;pFont = 字体;pbtn = 按钮数组;btnNum = 按钮数量extern void SDL_DrawButton(SDL_Renderer *pRenderer, TTF_Font *pFont, SDL_Button *pbtn, int btnNum);// 坐标是否在有效按钮上// 参数:x,y = 坐标;pbtn = 按钮数组;btnNum = 按钮数量// 返回值:按钮ID,或者 -1(不在有效按钮上)extern int SDL_isOnButton(int x, int y, SDL_Button *pbtn, int btnNum);#endif
// button.c// SDL2 自定义部件 - 按钮#include "button.h"#include "GetTexture.h"// 坐标是否在有效按钮上// 参数:x,y = 坐标;pbtn = 按钮数组;btnNum = 按钮数量// 返回值:按钮ID,或者 -1(不在有效按钮上)int SDL_isOnButton(int x, int y, SDL_Button *pbtn, int btnNum){ for(int i=0; i= pbtn[i].x && x <= pbtn[i].x + pbtn[i].w && y >= pbtn[i].y && y <= pbtn[i].y + pbtn[i].h) return pbtn[i].id; return -1;}// 画按钮// 参数:pRenderer = 渲染器;pFont = 字体;pbtn = 按钮数组;btnNum = 按钮数量void SDL_DrawButton(SDL_Renderer *pRenderer, TTF_Font *pFont, SDL_Button *pbtn, int btnNum){ int BackGroundColor, TextColor; Uint8 UpLineColor, DownLineColor; SDL_Texture *pBackGroundTexture; SDL_Texture *pTextTexture; SDL_Rect rt; for(int i=0; i
然后在消息循环里处理鼠标按键消息、移动消息
// Five.c// SDL2 五子棋//#define _DEBUG_#include#include #include #include #include #include #include "FiveData.h"#include "GetTexture.h"#include "button.h"// 资源文件char *ImageFileName[] ={ "Resource/BackGround.jpg", // 棋盘背景图文件 "Resource/BlackPiece.jpg", // 黑棋子图文件 "Resource/WhitePiece.jpg" // 白棋子图文件};int BackColor = 0xFFFFFF;// 棋子图片的背景色char FontFileName[] = "C:/Windows/Fonts/msyh.ttf"; // 字体文件char SoundFileName[] = "Resource/Stone.mp3"; // 落子音效文件// 字符串常量char szWindowTitle[] = "SDL2 五子棋";char *szWho[] ={ "黑方", "白方"};char *szGameTips[] ={ "第 %d 手,轮到 %s 落子", "共 %d 手,%s 取得本局胜利"};_Bool OnKeyUp(int x, int y, int nSpacing);void DrawBoard(SDL_Renderer *pRenderer, int nSpacing, int color);void DrawPieces(SDL_Renderer *pRenderer, int nSpacing, SDL_Texture **pImageTexture);void PrintString(SDL_Renderer *pRenderer, int nSpacing, char *text, TTF_Font *pFont, int color);void UpdateWindow(SDL_Window *pWindow, SDL_Renderer *pRenderer, int nSpacing, TTF_Font *pFont, SDL_Texture **pImageTexture, SDL_Button *pBtn, int n);#undef mainint main(int argc, char **argv){ int nWindowWidth = 640; // 屏幕尺寸 int nWindowHeight = 480; int nSpacing; // 棋盘线距 SDL_Window *pWindow; // 主窗口 SDL_Renderer *pRenderer; // 主窗口渲染器 SDL_Texture *pImageTexture[3]; // 棋盘背景、黑白棋子图纹理 TTF_Font *pFont; // 提示文字字体 Mix_Music *pMusic; // 音效 SDL_Event event; // 事件 _Bool bRun; // 持续等待事件控制循环标识 // 按钮 int id; int btnNum = 2; SDL_Button btn[2] = { {0, 0,0,0,0, "新局", 0, BTN_STATE_NORMAL}, {1, 0,0,0,0, "悔棋", 0, BTN_STATE_NORMAL} }; // 初始化:SDL2、SDL_Image(jpg)、SDL_ttf、SDL_Mixer if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 4096) == -1 || SDL_Init(SDL_INIT_VIDEO) == -1 || IMG_Init(IMG_INIT_JPG) == -1 || TTF_Init() == -1) {#ifdef _DEBUG_ fprintf(stderr, "1 %s", SDL_GetError());#endif return 1; } // 创建主窗口及其渲染器 if(SDL_CreateWindowAndRenderer(640, 480, SDL_WINDOW_RESIZABLE, &pWindow, &pRenderer) == -1) {#ifdef _DEBUG_ fprintf(stderr, "2 %s", SDL_GetError());#endif goto label_error; } SDL_SetWindowTitle(pWindow, szWindowTitle); // 加载图片文件 if(NULL == (pImageTexture[0] = GetImageTexture(pRenderer, ImageFileName[0], 0, 0)) || NULL == (pImageTexture[1] = GetImageTexture(pRenderer, ImageFileName[1], 1, BackColor)) || NULL == (pImageTexture[2] = GetImageTexture(pRenderer, ImageFileName[2], 1, BackColor))) {#ifdef _DEBUG_ fprintf(stderr, "3 %s", IMG_GetError());#endif goto label_error; } // 加载字体文件 if(NULL == (pFont = TTF_OpenFont(FontFileName, 20))) {#ifdef _DEBUG_ fprintf(stderr, "4 %s", TTF_GetError());#endif goto label_error; } // 加载声音文件 if((pMusic = Mix_LoadMUS(SoundFileName)) == NULL) {#ifdef _DEBUG_ fprintf(stderr, "5 %s", Mix_GetError());#endif goto label_error; } // 重置棋局数据,等待事件 Five_ResetData(); bRun = 1; while(bRun && SDL_WaitEvent(&event)) { switch(event.type) { case SDL_MOUSEMOTION : // 鼠标移动 // 鼠标在某个按钮上,鼠标左键压下则该按钮处于凹状态,无鼠标键压下则该按钮处于凸状态 if((id = SDL_isOnButton(event.button.x, event.button.y, btn, 2)) >= 0) { if(event.motion.state == SDL_BUTTON_LMASK) btn[id].state = BTN_STATE_DOWN; else btn[id].state = BTN_STATE_UP; } // 鼠标不在按钮上,则所有按钮正常显示 else { for(int i=0; i = 0) { btn[id].state = BTN_STATE_DOWN; UpdateWindow(pWindow, pRenderer, nSpacing, pFont, pImageTexture, btn, btnNum); } break; case SDL_MOUSEBUTTONUP : // 鼠标按键弹起,在某个按钮上则响应功能,在棋盘则检测落子 id = SDL_isOnButton(event.button.x, event.button.y, btn, 2); // 新局 if(id == 0) { btn[0].enable = 0; btn[1].enable = 0; Five_ResetData(); } // 悔棋 else if(id == 1) { btn[1].state = BTN_STATE_UP; // TODO ... } // 有效落子 else if(id < 0 && g_iWho != NONE && OnKeyUp(event.button.x, event.button.y, nSpacing)) { Mix_PlayMusic(pMusic, 0); btn[0].enable = 1; btn[1].enable = 1; if(Five_isFive()) g_iWho = NONE; } UpdateWindow(pWindow, pRenderer, nSpacing, pFont, pImageTexture, btn, btnNum); break; case SDL_WINDOWEVENT : // 有窗口消息,重新计算窗口尺寸 SDL_GetWindowSize(pWindow, &nWindowWidth, &nWindowHeight); nSpacing = SDL_min(nWindowWidth, nWindowHeight)/(MAX_LINES+2); UpdateWindow(pWindow, pRenderer, nSpacing, pFont, pImageTexture, btn, btnNum); break; case SDL_QUIT : bRun = 0; break; default : break; } }label_error:// 清理 if(pImageTexture[0] != NULL) SDL_DestroyTexture(pImageTexture[0]); if(pImageTexture[1] != NULL) SDL_DestroyTexture(pImageTexture[1]); if(pImageTexture[2] != NULL) SDL_DestroyTexture(pImageTexture[2]); if(pFont != NULL) TTF_CloseFont(pFont); if(pMusic != NULL) Mix_FreeMusic(pMusic); Mix_CloseAudio(); TTF_Quit(); IMG_Quit(); SDL_Quit(); return 0;}// 重绘窗口void UpdateWindow(SDL_Window *pWindow, SDL_Renderer *pRenderer, int nSpacing, TTF_Font *pFont, SDL_Texture **pImageTexture, SDL_Button *pBtn, int btnNum){ char szString[256]; SDL_RenderClear(pRenderer); SDL_RenderCopyEx(pRenderer, pImageTexture[0], NULL, NULL, 0, NULL, SDL_FLIP_NONE); DrawBoard(pRenderer, nSpacing, 0); DrawPieces(pRenderer, nSpacing, pImageTexture); sprintf(szString, szGameTips[g_iWho==NONE], g_nHands+(g_iWho!=NONE), szWho[(g_nHands+(g_iWho==NONE))%2]); PrintString(pRenderer, nSpacing, szString, pFont, 0); pBtn[0].x = nSpacing * (MAX_LINES+1); pBtn[1].x = nSpacing * (MAX_LINES+4); pBtn[1].y = pBtn[0].y = nSpacing; pBtn[1].w = pBtn[0].w = nSpacing * 2; pBtn[1].h = pBtn[0].h = nSpacing; SDL_DrawButton(pRenderer, pFont, pBtn, btnNum); SDL_RenderPresent(pRenderer);}// 响应落子按键// 参数:(x,y) = 被点击的窗口坐标;nSpacing = 棋盘线距_Bool OnKeyUp(int x, int y, int nSpacing){ // 计算落点棋盘坐标 int m = (x - 0.5*nSpacing)/nSpacing; int n = (y - 0.5*nSpacing)/nSpacing; // 处理有效落点 if(m>=0 && m =0 && n >16, (color>>8)&0xFF, color&0xFF, SDL_ALPHA_OPAQUE); SDL_RenderFillRect(pRenderer, &rt);}// 画棋盘// 参数:pRenderer = 渲染器;nSpacing = 棋盘线距;color = 线及星颜色void DrawBoard(SDL_Renderer *pRenderer, int nSpacing, int color){ int r, x, y, z; // 棋盘线 SDL_SetRenderDrawColor(pRenderer, color>>16, (color>>8)&0xFF, color&0xFF, SDL_ALPHA_OPAQUE); for(int i = 1; i <= MAX_LINES; i++) { SDL_RenderDrawLine(pRenderer, nSpacing, i*nSpacing, MAX_LINES*nSpacing, i*nSpacing); SDL_RenderDrawLine(pRenderer, i*nSpacing, nSpacing, i*nSpacing, MAX_LINES*nSpacing); } // 星位 r = nSpacing*0.2; // 星半径 x = nSpacing*4; // 第四线 y = nSpacing*(MAX_LINES+1)/2; // 中线 z = nSpacing*(MAX_LINES-3); // 倒数第四线 FillCircle(pRenderer, x, x, r, color); FillCircle(pRenderer, y, x, r, color); FillCircle(pRenderer, z, x, r, color); FillCircle(pRenderer, x, y, r, color); FillCircle(pRenderer, y, y, r, color); FillCircle(pRenderer, z, y, r, color); FillCircle(pRenderer, x, z, r, color); FillCircle(pRenderer, y, z, r, color); FillCircle(pRenderer, z, z, r, color);}// 画棋子// 参数:pRenderer = 渲染器;nSpacing = 棋盘线距;pImageTexture = 棋子纹理void DrawPieces(SDL_Renderer *pRenderer, int nSpacing, SDL_Texture **pImageTexture){ int r = 0.4*nSpacing; // 棋子半径 SDL_Rect rt = {0, 0, 2*r, 2*r}; if(g_nHands <= 0) return; for(int i=0; i
数据处理部分没有变化。悔棋按钮没有起作用,是因为涉及修改数据处理部分,懒得弄了。