您的位置:首页 > 其它

SDL2 自己画按钮

2016-02-14 00:00 363 查看
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]
==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);
}
}


数据处理部分没有变化。悔棋按钮没有起作用,是因为涉及修改数据处理部分,懒得弄了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: