SDL2 没有自带控件,今天尝试自己画个按钮。
用图片按钮是最简单的:找几幅图片,代表不同的按钮状态,根据需要显示即可。
这里实现一个windows标准风格、文字的,可以通用,不用带着不同的图片跑了——图片到底大,而且不通用。
首先把那个五子棋程序里的取得纹理的几个函数独立成一个库,方便调用
// GetTexture.h // SDL2 公共函数 - 取得 RGB、文字、图片的纹理 #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> #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 <SDL2/SDL.h> #include <SDL2/SDL_ttf.h> #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<btnNum; i++) if(pbtn[i].enable && x >= 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<btnNum; i++) { if(pbtn[i].enable) { switch(pbtn[i].state) { case BTN_STATE_NORMAL : BackGroundColor = 0xC5C5C5; TextColor = 0; UpLineColor = 0xFF; DownLineColor = 0; break; case BTN_STATE_DOWN : BackGroundColor = 0xA0A0A0; TextColor = 0; UpLineColor = 0; DownLineColor = 0xFF; break; case BTN_STATE_UP : BackGroundColor = 0xF1F1F1; TextColor = 0; UpLineColor = 0xFF; DownLineColor = 0; break; default : break; } } else { BackGroundColor = 0xF1F1F1; TextColor = 0x989898; UpLineColor = 0xFF; DownLineColor = 0; } pBackGroundTexture = GetRGBTexture(pRenderer, BackGroundColor, pbtn[i].w, pbtn[i].h); pTextTexture = GetTextTexture(pRenderer, pbtn[i].text, pFont, TextColor); if(pBackGroundTexture != NULL && pTextTexture != NULL) { rt.x = pbtn[i].x; rt.y = pbtn[i].y; rt.w = pbtn[i].w; rt.h = pbtn[i].h; SDL_RenderCopyEx(pRenderer, pBackGroundTexture, NULL, &rt, 0, NULL, SDL_FLIP_NONE); SDL_RenderCopyEx(pRenderer, pTextTexture, NULL, &rt, 0, NULL, SDL_FLIP_NONE); SDL_SetRenderDrawColor(pRenderer, UpLineColor, UpLineColor, UpLineColor, SDL_ALPHA_OPAQUE); SDL_RenderDrawLine(pRenderer, rt.x, rt.y, rt.x+rt.w, rt.y); SDL_RenderDrawLine(pRenderer, rt.x, rt.y, rt.x, rt.y+rt.h); SDL_SetRenderDrawColor(pRenderer, DownLineColor, DownLineColor, DownLineColor, SDL_ALPHA_OPAQUE); SDL_RenderDrawLine(pRenderer, rt.x+rt.w, rt.y, rt.x+rt.w, rt.y+rt.h); SDL_RenderDrawLine(pRenderer, rt.x, rt.y+rt.h, rt.x+rt.w, rt.y+rt.h); } if(pBackGroundTexture != NULL) SDL_DestroyTexture(pBackGroundTexture); if(pTextTexture != NULL) SDL_DestroyTexture(pTextTexture); } }
然后在消息循环里处理鼠标按键消息、移动消息
// Five.c // SDL2 五子棋 //#define _DEBUG_ #include <stdio.h> #include <string.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> #include <SDL2/SDL_Mixer.h> #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 main int 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<btnNum; i++) btn[i].state = BTN_STATE_NORMAL; } UpdateWindow(pWindow, pRenderer, nSpacing, pFont, pImageTexture, btn, btnNum); break; case SDL_MOUSEBUTTONDOWN : // 鼠标按键按下某个按钮,该按钮处于凹状态 if((id = SDL_isOnButton(event.button.x, event.button.y, btn, 2)) >= 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<MAX_LINES && n>=0 && n<MAX_LINES && g_iBoard[m][n]==NONE) { Five_AddPiece(m, n, g_iWho); return 1; } return 0; } // 画圆(SDL2 没有画圆的函数,先用矩形框代替吧) // 参数:pRenderer = 渲染器;(x,y) = 圆心坐标;r = 半径;color = 填充色 void FillCircle(SDL_Renderer *pRenderer, int x, int y, int r, int color) { SDL_Rect rt = {x-r, y-r, 2*r, 2*r}; SDL_SetRenderDrawColor(pRenderer, color>>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<MAX_LINES; i++) { for(int j=0; j<MAX_LINES; j++) { rt.x = (i+1)*nSpacing - r; rt.y = (j+1)*nSpacing - r; if(g_iBoard[i][j] == BLACK) SDL_RenderCopyEx(pRenderer, pImageTexture[1], NULL, &rt, 0, NULL, SDL_FLIP_NONE); else if(g_iBoard[i][j] == WHITE) SDL_RenderCopyEx(pRenderer, pImageTexture[2], NULL, &rt, 0, NULL, SDL_FLIP_NONE); } } } // 提示文字 // 参数:pRenderer = 渲染器;nSpacing = 棋盘线距;text = 文字内容;pFont = 字体;color = 文字颜色 void PrintString(SDL_Renderer *pRenderer, int nSpacing, char *text, TTF_Font *pFont, int color) { SDL_Texture *pTextTexture; SDL_Rect rt; rt.x = nSpacing; rt.y = nSpacing*(MAX_LINES+1); rt.w = nSpacing*strlen(text)/4; // 这个 4 和字体大小有关 rt.h = nSpacing; if((pTextTexture = GetTextTexture(pRenderer, text, pFont, color)) != NULL) { SDL_RenderCopyEx(pRenderer, pTextTexture, NULL, &rt, 0, NULL, SDL_FLIP_NONE); SDL_DestroyTexture(pTextTexture); } }
数据处理部分没有变化。悔棋按钮没有起作用,是因为涉及修改数据处理部分,懒得弄了。