邱 璇洛 (ゝ∀・)

邱 璇洛 (ゝ∀・)

你好哇(*゚∀゚*)~这里是邱璇洛的博客,常常用来记录一些技术文章和小日常~(σ゚∀゚)σ
twitter
tg_channel

SDL2學習筆記 - 滑鼠事件和拖曳

開發環境 : MacOS
參考資料 : 陳雲老師的 SDL2 教程

在 SDL2 中實現鼠標事件還是比較簡單的,只需要在event_loop(事件循環)裡加個switch或者if就可以的實現:

/* 事件循環 */
void event_loop() {
    while (1) {

        ...

        // 鼠標事件
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if(event.type == SDL_QUIT) {
                return;
            } 
        }

        ...
    }
}

但如果要實現拖拽事件,我們就需要大刀闊斧的改造一下了。
首先使用面向對象思維抽象化RectShape,在頭文件裡做一個結構體和幾個必須的函數。

// 抽象矩形框
#ifndef MOUSEEVENT_RECTSHAPE_H
#define MOUSEEVENT_RECTSHAPE_H

#include <SDL.h>

typedef struct {
    SDL_FRect dest;
    uint32_t color;
    // _代表內部使用
    // 記錄起始點
    SDL_FPoint _dragStartPoint;
    // 記錄開始拖動時鼠標的位置 了解鼠標移動的距離
    SDL_FPoint _dragStartMousePoint;
    // 是否被點擊(是否啟用)
    int _dragEnabled;
} RectShape;

// 創建矩形框
RectShape *RectShape_Create(float x, float y, float w, float h, uint32_t color);

// 繪圖
void RectShape_Draw(RectShape *self, SDL_Renderer *renderer);

// 鼠標事件 .. SDL_Event是事件類型
void RectShape_OnMouseEvent(RectShape *self, SDL_Event *event);

// 銷毀
void RectShape_Destroy(RectShape *self);

#endif

然後,我們逐一實現它:

首先是創建矩形框,這個比較簡單:

...

// 創建矩形框
RectShape *RectShape_Create(float x, float y, float w, float h, uint32_t color) {
    // 分配內存
    RectShape *self = malloc(sizeof(RectShape));
    self->color = color;
    self->dest.x = x;
    self->dest.y = y;
    self->dest.w = w;
    self->dest.h = h;
    // 默認不啟用拖動屬性
    self->_dragEnabled = 0;
    return self;
}

...

_dragEnabled是確定是否啟用拖拽屬性的,確保鼠標沒按下時不啟動屬性(直接啟用不成自動尋路了)

然後基本的銷毀和繪圖

...

// 繪製
void RectShape_Draw(RectShape *self, SDL_Renderer *renderer) {
    // 提取顏色通道
    // 0xffff0000 ARGB
    SDL_SetRenderDrawColor(
        renderer,
        // RGBA
        // 提取紅色通道值,右移xx位
        (self->color & 0x00ff0000) >> 16, // 紅    r
        (self->color & 0x0000ff00) >> 8,  // 綠    g
        self->color & 0x000000ff,         // 藍    b
        (self->color & 0xff000000) >> 24  // 透明  a
    );
    // &取地址
    SDL_RenderFillRectF(renderer, &(self->dest));
}

...

// 銷毀
void RectShape_Destroy(RectShape *self) {
    free(self);
}

dest->color不能直接用,需要用右移計算提取裡面的元素(顏色 RGBA)

最重要的來了,怎麼搞出來拖拽這一事件呢?

// 鼠標事件(拖動) .. SDL_Event是事件類型
void RectShape_OnMouseEvent(RectShape *self, SDL_Event *event) {
    switch (event->type) {
        // 鼠標移動事件
        case SDL_MOUSEMOTION:
            // 點擊時開始和鼠標一起拖動
            if (self->_dragEnabled == 1) {
                self->dest.x = self->_dragStartPoint.x + event->motion.x - self->_dragStartMousePoint.x;
                self->dest.y = self->_dragStartPoint.y + event->motion.y - self->_dragStartMousePoint.y;
            }
            break;
        // 鼠標點下事件
        case SDL_MOUSEBUTTONDOWN:
            // 當鼠標按下時,開始拖動
            self->_dragStartPoint.x = self->dest.x;
            self->_dragStartPoint.y = self->dest.y;
            self->_dragStartMousePoint.x = event->button.x;
            self->_dragStartMousePoint.y = event->button.y;
            self->_dragEnabled = 1;
            break;
        // 鼠標彈起事件
        case SDL_MOUSEBUTTONUP:
            self->_dragEnabled = 0;
            break;
    }
}

還有限定區域:只能拖動方塊區域才能拖動的方法

// 鼠標事件(拖動) .. SDL_Event是事件類型
void RectShape_OnMouseEvent(RectShape *self, SDL_Event *event) {
    // 檢測碰撞
    SDL_Point currentMousePoint;
    // 轉換類型
    SDL_Rect destRect;
    
    switch (event->type) {
        // 鼠標移動事件
        case SDL_MOUSEMOTION:
            // 點擊時開始和鼠標一起拖動
            if (self->_dragEnabled == 1) {
                self->dest.x = self->_dragStartPoint.x + event->motion.x - self->_dragStartMousePoint.x;
                self->dest.y = self->_dragStartPoint.y + event->motion.y - self->_dragStartMousePoint.y;
            }
            break;
        // 鼠標點下事件
        case SDL_MOUSEBUTTONDOWN:
            // 檢測碰撞(鼠標和紅色塊)
            currentMousePoint.x = event->button.x;
            currentMousePoint.y = event->button.y;
            // 強制類型轉換
            destRect.x = (int) self->dest.x;
            destRect.y = (int) self->dest.y;
            destRect.w = (int) self->dest.w;
            destRect.h = (int) self->dest.h;
            // 如果他倆相交
            if (SDL_PointInRect(&currentMousePoint, &destRect)) {
                // 當鼠標按下時,開始拖動
                self->_dragStartPoint.x = self->dest.x;
                self->_dragStartPoint.y = self->dest.y;
                self->_dragStartMousePoint.x = event->button.x;
                self->_dragStartMousePoint.y = event->button.y;
                self->_dragEnabled = 1;
            }
            break;
        // 鼠標彈起事件
        case SDL_MOUSEBUTTONUP:
            self->_dragEnabled = 0;
            break;
    }
}

然後在 main 裡畫一個小方塊,配置一下

#include <SDL.h>
#include "RectShape.h"

#define WIDTH 400
#define HEIGHT 300
/* 設定幀頻 */
#define FRAMERATE 60

// 渲染器
SDL_Renderer *renderer; 
// window
SDL_Window *window;
// RectShape
RectShape *rect;


void draw() {
    // 渲染背景為白色,使用渲染器
    SDL_SetRenderDrawColor(renderer, 225, 225, 225, 225);
    SDL_RenderClear(renderer);

    RectShape_Draw(rect, renderer);

    // 使其生效(放到螢幕上)
    SDL_RenderPresent(renderer);
}

/* 事件循環 */
void event_loop() {
    while (1) {
        /* 幀率:獲取毫秒值 */
        long begin = SDL_GetTicks();
        /* 動畫 */
        draw();

        SDL_Event event;
        /* 使用循環讀取事件比if快,不用等 */
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                // 鼠標移動事件
                case SDL_MOUSEMOTION:

                // 鼠標點下事件
                case SDL_MOUSEBUTTONDOWN:
                // 鼠標彈起事件
                case SDL_MOUSEBUTTONUP:
                    // 轉移到函數內部處理事件
                    RectShape_OnMouseEvent(rect, &event);
                    break;
                case SDL_QUIT: return;
            }
        }

        /* 幀率:當前值 */
        long current = SDL_GetTicks();
        /* 現在每一幀花費的時間 */
        long cost = current - begin;
        /* 每幀應該的時間 */
        long frame = 1000/FRAMERATE;
        /* 計算該休眠的時間維持幀數 */
        long delay = frame - cost;
        /* 如果delay是負就不休眠維持幀數 */
        if (delay > 0) {
            SDL_Delay(delay);
        }
        /* 在系統資源足夠的情況下能夠保持一個足夠的幀頻,不夠咱也沒辦法 */
    }
}

int main() {

    if (SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Can not init video, %s", SDL_GetError());
        return 1;
    }

    window = SDL_CreateWindow(
        "鼠標事件",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        WIDTH,HEIGHT,
        SDL_WINDOW_SHOWN
    );

    if (window==NULL) {
        SDL_Log("Can not create window, %s", SDL_GetError());
        return 1;
    }

    // 創建渲染器
    // 窗口 渲染驅動(索引) 用什麼渲染
    // SDL_RENDERER_ACCELERATED 硬件加速 GPU
    // SDL_RENDERER_SOFTWARE CPU渲染 (什麼MC行為)
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if (renderer==NULL) {
        SDL_Log("Can not create renderer, %s", SDL_GetError());
    }

    // 創建
    rect = RectShape_Create(0, 0, 80, 60, 0xffff0000);

    event_loop();
    // 銷毀
    RectShape_Destroy(rect);
    /* 釋放渲染器 */
    SDL_DestroyRenderer(renderer);
    /* 釋放窗口 */
    SDL_DestroyWindow(window);
    return 0;
}
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。