Development Environment: MacOS
Reference Material: Teacher Chen Yun's SDL2 Tutorial
Implementing mouse events in SDL2 is relatively simple; you just need to add a switch
or if
in the event_loop
:
/* Event Loop */
void event_loop() {
while (1) {
...
// Mouse Events
SDL_Event event;
while (SDL_PollEvent(&event)) {
if(event.type == SDL_QUIT) {
return;
}
}
...
}
}
However, to implement drag events, we need to make some significant modifications.
First, we abstract RectShape
using object-oriented thinking, creating a structure and several necessary functions in the header file.
// Abstract Rectangle
#ifndef MOUSEEVENT_RECTSHAPE_H
#define MOUSEEVENT_RECTSHAPE_H
#include <SDL.h>
typedef struct {
SDL_FRect dest;
uint32_t color;
// _ indicates internal use
// Record starting point
SDL_FPoint _dragStartPoint;
// Record mouse position when dragging starts to understand mouse movement distance
SDL_FPoint _dragStartMousePoint;
// Whether it is clicked (enabled)
int _dragEnabled;
} RectShape;
// Create Rectangle
RectShape *RectShape_Create(float x, float y, float w, float h, uint32_t color);
// Drawing
void RectShape_Draw(RectShape *self, SDL_Renderer *renderer);
// Mouse Events .. SDL_Event is the event type
void RectShape_OnMouseEvent(RectShape *self, SDL_Event *event);
// Destroy
void RectShape_Destroy(RectShape *self);
#endif
Next, we implement it step by step:
First, creating the rectangle, which is quite simple:
...
// Create Rectangle
RectShape *RectShape_Create(float x, float y, float w, float h, uint32_t color) {
// Allocate memory
RectShape *self = malloc(sizeof(RectShape));
self->color = color;
self->dest.x = x;
self->dest.y = y;
self->dest.w = w;
self->dest.h = h;
// Default drag property is not enabled
self->_dragEnabled = 0;
return self;
}
...
_dragEnabled
determines whether the drag property is enabled, ensuring that the property does not activate when the mouse is not pressed (enabling it directly would lead to automatic pathfinding).
Then, the basic destroy and draw functions:
...
// Draw
void RectShape_Draw(RectShape *self, SDL_Renderer *renderer) {
// Extract color channels
// 0xffff0000 ARGB
SDL_SetRenderDrawColor(
renderer,
// RGBA
// Extract red channel value, right shift xx bits
(self->color & 0x00ff0000) >> 16, // Red r
(self->color & 0x0000ff00) >> 8, // Green g
self->color & 0x000000ff, // Blue b
(self->color & 0xff000000) >> 24 // Alpha a
);
// & takes address
SDL_RenderFillRectF(renderer, &(self->dest));
}
...
// Destroy
void RectShape_Destroy(RectShape *self) {
free(self);
}
dest->color
cannot be used directly; it needs to be calculated using right shifts to extract the elements (color RGBA).
The most important part: how to implement the drag event?
// Mouse Events (Dragging) .. SDL_Event is the event type
void RectShape_OnMouseEvent(RectShape *self, SDL_Event *event) {
switch (event->type) {
// Mouse motion event
case SDL_MOUSEMOTION:
// Start dragging with the mouse when clicked
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;
// Mouse button down event
case SDL_MOUSEBUTTONDOWN:
// Start dragging when the mouse is pressed
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;
// Mouse button up event
case SDL_MOUSEBUTTONUP:
self->_dragEnabled = 0;
break;
}
}
Additionally, to restrict the area: only allow dragging within the rectangle's area.
// Mouse Events (Dragging) .. SDL_Event is the event type
void RectShape_OnMouseEvent(RectShape *self, SDL_Event *event) {
// Check for collision
SDL_Point currentMousePoint;
// Type conversion
SDL_Rect destRect;
switch (event->type) {
// Mouse motion event
case SDL_MOUSEMOTION:
// Start dragging with the mouse when clicked
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;
// Mouse button down event
case SDL_MOUSEBUTTONDOWN:
// Check for collision (mouse and red block)
currentMousePoint.x = event->button.x;
currentMousePoint.y = event->button.y;
// Force type conversion
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 they intersect
if (SDL_PointInRect(¤tMousePoint, &destRect)) {
// Start dragging when the mouse is pressed
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;
// Mouse button up event
case SDL_MOUSEBUTTONUP:
self->_dragEnabled = 0;
break;
}
}
Then, in the main function, draw a small rectangle and configure it:
#include <SDL.h>
#include "RectShape.h"
#define WIDTH 400
#define HEIGHT 300
/* Set frame rate */
#define FRAMERATE 60
// Renderer
SDL_Renderer *renderer;
// Window
SDL_Window *window;
// RectShape
RectShape *rect;
void draw() {
// Render background as white, using the renderer
SDL_SetRenderDrawColor(renderer, 225, 225, 225, 225);
SDL_RenderClear(renderer);
RectShape_Draw(rect, renderer);
// Make it effective (put it on the screen)
SDL_RenderPresent(renderer);
}
/* Event Loop */
void event_loop() {
while (1) {
/* Frame rate: get milliseconds */
long begin = SDL_GetTicks();
/* Animation */
draw();
SDL_Event event;
/* Using a loop to read events is faster than if, no need to wait */
while (SDL_PollEvent(&event)) {
switch (event.type) {
// Mouse motion event
case SDL_MOUSEMOTION:
// Mouse button down event
case SDL_MOUSEBUTTONDOWN:
// Mouse button up event
case SDL_MOUSEBUTTONUP:
// Transfer to function to handle events
RectShape_OnMouseEvent(rect, &event);
break;
case SDL_QUIT: return;
}
}
/* Frame rate: current value */
long current = SDL_GetTicks();
/* Time spent on each frame */
long cost = current - begin;
/* Expected time for each frame */
long frame = 1000/FRAMERATE;
/* Calculate the time to sleep to maintain frame rate */
long delay = frame - cost;
/* If delay is negative, do not sleep to maintain frame rate */
if (delay > 0) {
SDL_Delay(delay);
}
/* Maintain a sufficient frame rate if system resources are adequate; otherwise, we can't help it */
}
}
int main() {
if (SDL_Init(SDL_INIT_VIDEO)) {
SDL_Log("Cannot initialize video, %s", SDL_GetError());
return 1;
}
window = SDL_CreateWindow(
"Mouse Events",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WIDTH,HEIGHT,
SDL_WINDOW_SHOWN
);
if (window==NULL) {
SDL_Log("Cannot create window, %s", SDL_GetError());
return 1;
}
// Create renderer
// Window, rendering driver (index), what to render
// SDL_RENDERER_ACCELERATED hardware acceleration GPU
// SDL_RENDERER_SOFTWARE CPU rendering (what MC behavior)
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer==NULL) {
SDL_Log("Cannot create renderer, %s", SDL_GetError());
}
// Create
rect = RectShape_Create(0, 0, 80, 60, 0xffff0000);
event_loop();
// Destroy
RectShape_Destroy(rect);
/* Release renderer */
SDL_DestroyRenderer(renderer);
/* Release window */
SDL_DestroyWindow(window);
return 0;
}