Hi everyone! It’s been a while since my last SDL2 article (see all SDL2 articles). Today, I’m going to show how you can use the mouse to drag an object across an SDL2 window.
Drag and drop is nowadays a standard feature of any Multiple Document Interface (MDI). Although MDI as a user interface layout has fallen out of fashion in mainstream applications (even GNOME seems to think we don’t need it), it has enabled so many things ranging from windows in operating system GUIs to inventory containers in games.
The source code for this article is available in the SDL2DragDrop folder at the Gigi Labs BitBucket repository.
Displaying an Empty Window
Let’s start out by displaying an empty window. We can use the code below for this:
#include <SDL2/SDL.h>
int main(int argc, char ** argv)
{
// variables
bool quit = false;
SDL_Event event;
// init SDL
SDL_Init(SDL_INIT_VIDEO);
SDL_Window * window = SDL_CreateWindow("SDL2 Drag and Drop",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);
// handle events
while (!quit)
{
SDL_Delay(10);
SDL_PollEvent(&event);
switch (event.type)
{
case SDL_QUIT:
quit = true;
break;
}
SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
}
// cleanup SDL
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Most of this should be familiar from “Showing an Empty Window in SDL2“, but there are a few differences:
- The
#include
on the first line is different from that of many of my previous SDL2 articles. That’s because I’m now using SDL2 on Linux (see “How to Set Up SDL2 on Linux“). - We’re using SDL_RenderClear() to give the window a background colour (as we’ve done in a few earlier articles).
- We’re using SDL_PollEvent() to check for events all the time (combined with SDL_Delay() to occasionally give the CPU a break). This is also something we’ve done a few times before.
On Linux, assuming my file is called main.cpp, I can compile this from the command-line as follows:
g++ main.cpp -lSDL2 -lSDL2main -o sdl2exe
Just to be super clear (as this has been a point of confusion in earlier articles), I’m using the C++ compiler. We’ll be using a couple of C++ features, so please don’t try to compile this article’s code as-is with a C compiler.
Once this compiles successfully, I can run the executable that was produced as a result:
./sdl2exe
…and I get an empty window:
Adding Rectangles
We’re going to need something we can drag around, so let’s add a couple of rectangles. Again, we’ve done this before (see “SDL2 Bounding Box Collision Detection“), but we’ll improve a little by storing our rectangles in a C++ STL list.
First, add the necessary #include
at the top of the file:
#include <list>
With this in place, we can now create a couple of SDL_Rect
s and put them in a list. The following code goes before the event loop.
SDL_Rect rect1 = { 288, 208, 100, 100 };
SDL_Rect rect2 = { 50, 50, 100, 80 };
std::list<SDL_Rect *> rectangles;
rectangles.push_back(&rect1);
rectangles.push_back(&rect2);
Towards the end of the event loop, we can add code to draw those rectangles. Since we’re using a list, we can add more rectangles if we want, and this part of the code isn’t going to change. Note that I’m also using a little C++11 shortcut to iterate through the list.
SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
SDL_RenderClear(renderer);
for (auto const& rect : rectangles)
{
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
SDL_RenderFillRect(renderer, rect);
}
SDL_RenderPresent(renderer);
If we compile and run this now, we get a couple of green rectangles:
Changing Colour on Click
Before we get to actually dragging those rectangles, we need a way to select one when it gets clicked, both in code and visually. For this, we’ll add a selectedRect
variable somewhere before the event loop:
SDL_Rect * selectedRect = NULL;
By default, no rectangle is selected, which is why this is set to NULL
.
We also need a couple more variables somewhere at the beginning of the program to help us keep track of mouse events:
bool leftMouseButtonDown = false;
SDL_Point mousePos;
In the switch
statement within the event loop, we can now start adding mouse event handlers. We’ll start with one for SDL_MOUSEMOTION
, which keeps track of the mouse coordinates in the mousePos
variable we just declared:
case SDL_MOUSEMOTION:
mousePos = { event.motion.x, event.motion.y };
break;
Adding another event handler for SDL_MOUSEBUTTONUP
, we clear the leftMouseButtonDown
flag and the selected rectangle when the left mouse button is released:
case SDL_MOUSEBUTTONUP:
if (leftMouseButtonDown && event.button.button == SDL_BUTTON_LEFT)
{
leftMouseButtonDown = false;
selectedRect = NULL;
}
break;
Finally, we add another event handler for SDL_MOUSEBUTTONDOWN
, which uses SDL_PointInRect() to identify the rectangle being clicked (if any), and sets it as the selected rectangle:
case SDL_MOUSEBUTTONDOWN:
if (!leftMouseButtonDown && event.button.button == SDL_BUTTON_LEFT)
{
leftMouseButtonDown = true;
for (auto rect : rectangles)
{
if (SDL_PointInRect(&mousePos, rect))
{
selectedRect = rect;
break;
}
}
}
break;
At this point, we can add some conditional logic in the rendering code to draw the selected rectangle (i.e. the one being clicked) in blue instead of green:
SDL_SetRenderDrawColor(renderer, 242, 242, 242, 255);
SDL_RenderClear(renderer);
for (auto const& rect : rectangles)
{
if (rect == selectedRect)
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
else
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
SDL_RenderFillRect(renderer, rect);
}
SDL_RenderPresent(renderer);
If we compile and run this now, we find that rectangles become blue when clicked:
Drag and Drop
That might have seemed like a lot of work just to highlight a rectangle when clicked, but in fact we have already implemented much of what we need for drag and drop. To finish the job, we’ll start by adding a new variable near the beginning of the program:
SDL_Point clickOffset;
This clickOffset
variable will store the point within the rectangle (relative to the rectangle’s boundary) where you clicked, so that as we move the rectangle by dragging, we can keep that same spot under the mouse pointer.
In fact, when the left mouse button is pressed, we will now store this location so that we can use it later in the code:
case SDL_MOUSEBUTTONDOWN:
if (!leftMouseButtonDown && event.button.button == SDL_BUTTON_LEFT)
{
leftMouseButtonDown = true;
for (auto rect : rectangles)
{
if (SDL_PointInRect(&mousePos, rect))
{
selectedRect = rect;
clickOffset.x = mousePos.x - rect->x;
clickOffset.y = mousePos.y - rect->y;
break;
}
}
}
break;
Then, while the mouse is moving, we update the selected rectangle’s position accordingly:
case SDL_MOUSEMOTION:
{
mousePos = { event.motion.x, event.motion.y };
if (leftMouseButtonDown && selectedRect != NULL)
{
selectedRect->x = mousePos.x - clickOffset.x;
selectedRect->y = mousePos.y - clickOffset.y;
}
}
break;
…and that is all that is needed to allow the user to click and drag those rectangles:
Wrapping Up
In this article, we’ve mostly built on concepts covered in earlier articles. By manipulating mouse events, we were able to add interactivity to rectangles. They change colour when clicked, and can be dragged around using the mouse.
There are a few things you’ll notice if you’re diligent enough:
- If you drag the rectangles around really fast, they lag a little behind the mouse pointer. I’m not really sure how to fix that.
- There isn’t really a proper z-index implementation, so it looks bizarre when you can drag a rectangle underneath another. This can probably be fixed by changing the order in which the rectangles are rendered, so that the selected one always appears on top of the rest.
- Using a list is okay for a few items, but if you have a lot of objects in your window, you might want to use a more efficient data structure such as a quadtree.