SDL2 自己画按钮

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

数据处理部分没有变化。悔棋按钮没有起作用,是因为涉及修改数据处理部分,懒得弄了。




转载于:https://my.oschina.net/u/580100/blog/614830

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenGL是一种图形库,它提供了一套用于绘制2D和3D图形的API。利用OpenGL,我们可以实现简单的板功能。 首先,我们需要创建一个OpenGL窗口来容纳我们的绘图。可以使用外部库,如GLFW或SDL来创建窗口。然后,我们需要设置窗口的大小和绘制模式。 在OpenGL中,绘制是通过调用绘制函数来实现的。为了实现板功能,我们可以使用鼠标和键盘事件来触发绘制函数。 当用户按下鼠标按钮时,我们可以将鼠标坐标与绘制区域坐标进行关联。然后,我们可以使用OpenGL的绘制函数来在相应的位置绘制图形。 例如,当用户按下鼠标左键并移动鼠标时,我们可以使用OpenGL的直线绘制函数来实时绘制线条。我们可以将当前鼠标位置与上一个鼠标位置进行连接,形成一条线。 类似地,当用户按下鼠标右键时,我们可以使用OpenGL的点绘制函数来在当前鼠标位置一个点。 另外,我们也可以使用OpenGL的矩形绘制函数来实现绘制矩形的功能。当用户按下键盘上的特定按键时,我们可以判断按键的类型,并调用相应的绘制函数来实现绘制矩形的功能。 最后,我们可以使用一个清除板的按钮,当用户点击该按钮时,我们可以通过调用OpenGL的清除函数来清除绘制区域,实现板的清空功能。 总的来说,通过使用OpenGL的绘制函数和鼠标键盘事件,以及相应的逻辑判断,我们可以实现一个简单的板功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值