開発環境 : 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(¤tMousePoint, &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;
// ウィンドウ
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("ビデオの初期化に失敗しました, %s", SDL_GetError());
return 1;
}
window = SDL_CreateWindow(
"マウスイベント",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WIDTH,HEIGHT,
SDL_WINDOW_SHOWN
);
if (window==NULL) {
SDL_Log("ウィンドウの作成に失敗しました, %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("レンダラーの作成に失敗しました, %s", SDL_GetError());
}
// 作成
rect = RectShape_Create(0, 0, 80, 60, 0xffff0000);
event_loop();
// 破棄
RectShape_Destroy(rect);
/* レンダラーを解放 */
SDL_DestroyRenderer(renderer);
/* ウィンドウを解放 */
SDL_DestroyWindow(window);
return 0;
}