邱 璇洛 (ゝ∀・)

邱 璇洛 (ゝ∀・)

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

SDL2 Learning Notes - Mouse Events and Dragging

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(&currentMousePoint, &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;
}
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.