SDL2 本身畫按鈕

SDL2 沒有自帶控件,今天嘗試本身畫個按鈕。
windows

用圖片按鈕是最簡單的:找幾幅圖片,表明不一樣的按鈕狀態,根據須要顯示便可。數組

這裏實現一個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;
}

在這個基礎上畫按鈕ui

// 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);
    }
}

而後在消息循環裏處理鼠標按鍵消息、移動消息spa

// 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}
    };


    // 初始化:SDL二、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);
    }
}

數據處理部分沒有變化。悔棋按鈕沒有起做用,是由於涉及修改數據處理部分,懶得弄了。指針

相關文章
相關標籤/搜索