We've built the basic structure and the actor menu. Now, let's implement the ability to interact with the actors in that menu. The goal is to allow users to click and drag an actor type to eventually place it in the level.
This lesson covers the first part of drag-and-drop: initiating the drag and providing visual feedback. We'll modify our Actor
class to recognize when it's been clicked.
Upon detecting a click, the Actor
will dispatch a custom SDL_Event
. We'll use SDL_RegisterEvents()
to define this event type.
Then, we'll introduce a new ActorTooltip
class. This class will manage a second, specialized SDL_Window
. This window will appear when the custom drag event is received, display the image of the actor being dragged, and follow the mouse cursor faithfully using SDL_GetGlobalMouseState()
and SDL_SetWindowPosition()
.
We'll carefully select window flags to ensure it behaves like a proper tooltip.
By the end of this section, we’ll be able to click and drag on the actors in our actor menu. We’ll have a visual indicator follow our mouse as we drag our actor, even outside of our primary window:
To signal that the user has started dragging an actor from the menu, we need a way for the Actor
instance to communicate this action. We'll achieve this by defining and registering a custom SDL event type specifically for this purpose.
// Config.h
// ...
namespace UserEvents{
#ifdef WITH_EDITOR
inline Uint32 ACTOR_DRAG{
SDL_RegisterEvents(1)};
#endif
}
// ...
First, we'll add a HasMouseFocus()
method to Actor
to specifically check if the cursor is currently over that particular actor's area. This function will need to query the ParentScene
to ensure the main window is active before checking the coordinates against the actor's Rect
.
Because this new function relies on the full definition of Scene
(which isn't available in Actor.h
due to the forward declaration), we need to separate declaration from definition. We’ll add a new source file for this and, to keep our header file concise, we’ll move our HandleEvent()
definition over to that new header file too.
We'll declare HasMouseFocus()
and HandleEvent()
in Actor.h
and implement them in a corresponding Editor/Source/Actor.cpp
file, where we can safely include Editor/Scene.h
.
// Editor/Actor.h
// ...
namespace Editor {
class Scene;
class Actor {
public:
// ...
bool HasMouseFocus() const;
virtual void HandleEvent(const SDL_Event& E){}
virtual void HandleEvent(const SDL_Event& E);
// ...
};
}
Inside Editor/Source/Actor.cpp
, the Actor::HasMouseFocus()
function first defers to the scene: if the parent scene doesn’t have mouse focus, we know the Actor
can’t possibly have mouse focus, so we return false
. This ensures we only consider hover checks when the editor window is active.
Assuming the scene has focus, SDL_GetMouseState()
fetches the mouse coordinates within that scene’s window. These coordinates are then compared against the actor's boundaries defined by Rect
.
If the coordinates lie within the horizontal range (Rect.x
to Rect.x + Rect.w
) and the vertical range (Rect.y
to Rect.y + Rect.h
), the actor is considered hovered, and true
is returned. Otherwise, false
indicates the mouse is elsewhere.
// Editor/Actor.cpp
#include "Editor/Actor.h"
#include "Editor/Scene.h"
using namespace Editor;
bool Actor::HasMouseFocus() const {
if (!ParentScene.HasMouseFocus()) {
return false;
}
int x, y;
SDL_GetMouseState(&x, &y);
if (
x < Rect.x ||
x > Rect.x + Rect.w ||
y < Rect.y ||
y > Rect.y + Rect.h
) {
return false;
}
return true;
}
The Actor::HandleEvent()
implementation filters for the specific interaction we care about: a left mouse button press (that is, where the event’s type
is SDL_MOUSEBUTTONDOWN
, and the SDL_MouseButtonEvent
's button
value is SDL_BUTTON_LEFT
).
If both are true, it then also verifies HasMouseFocus()
to confirm the click landed within this actor's bounds.
Upon confirmation, we prepare our custom event. An SDL_Event
struct called DragEvent
is initialized with the type UserEvents::ACTOR_DRAG
.
The key piece of information – which actor was clicked – is attached by assigning this
(a pointer to the current Actor
object) to DragEvent.user.data1
. This event, carrying the actor pointer, is then injected into the event queue via SDL_PushEvent(&DragEvent)
.
// Editor/Actor.cpp
// ...
void Actor::HandleEvent(const SDL_Event& E) {
if (
E.type == SDL_MOUSEBUTTONDOWN &&
E.button.button == SDL_BUTTON_LEFT &&
HasMouseFocus()
) {
SDL_Event DragEvent{UserEvents::ACTOR_DRAG};
DragEvent.user.data1 = this;
SDL_PushEvent(&DragEvent);
}
}
To make the dragging action clear, we need a visual indicator that follows the mouse. When the ACTOR_DRAG
event occurs, we should display the image of the selected actor near the cursor.
The best way to ensure this indicator remains visible even if the mouse moves outside the main editor window is to use a separate, dedicated SDL_Window
. This approach allows the tooltip to float freely above the main window and others.
SDL lets us create windows with special characteristics suitable for tooltips, which we’ll set up soon.
We will create an Editor::ActorTooltip
class to manage this process. It will be responsible for:
SDL_Window
with appropriate flagsACTOR_DRAG
eventOur functions to accomplish all of these tasks look like this, and we’ll walk through implementing them next:
// Editor/ActorTooltip.h
#pragma once
#include <SDL.h>
#include "Actor.h"
namespace Editor{
class Scene;
class ActorTooltip {
public:
ActorTooltip(Scene& ParentScene);
~ActorTooltip();
ActorTooltip(const ActorTooltip&) = delete;
ActorTooltip& operator=(const ActorTooltip&)
= delete;
void Render();
void Tick(float DeltaTime);
void PositionWindow();
void HandleEvent(const SDL_Event& E);
void SetIsVisible(bool NewVisibility);
SDL_Surface* GetSurface() const {
return SDL_GetWindowSurface(SDLWindow);
}
private:
bool isVisible{false};
SDL_Window* SDLWindow{nullptr};
Actor* DragActor{nullptr};
Scene& ParentScene;
};
}
ActorTooltip
ConstructorThe constructor for ActorTooltip
, defined in its .cpp file, focuses on creating the specialized SDL_Window
. The core is the call to SDL_CreateWindow()
, where the last argument specifies the window's characteristics through flags combined with |
.
Here's what each flag contributes:
SDL_WINDOW_HIDDEN
: Ensures the tooltip is initially hidden.SDL_WINDOW_TOOLTIP
: Provides a hint to the window manager about the window's purpose.SDL_WINDOW_BORDERLESS
: Creates a window without any standard decorations like title bars or borders.SDL_WINDOW_SKIP_TASKBAR
: Excludes the window from the taskbar, typical for tooltips or temporary popups.SDL_WINDOW_ALWAYS_ON_TOP
: Keeps the tooltip window rendered above the main application window and potentially other applications.The values we provide for the title, size and position arguments to SDL_CreateWindow()
aren’t important - we’ll just use some placeholder values. The borderless nature makes the title mostly irrelevant, and subsequent code will dynamically manage the tooltip's position and size when it becomes visible.
// Editor/Source/ActorTooltip.cpp
#include "Editor/ActorTooltip.h"
#include "Editor/Scene.h"
using namespace Editor;
ActorTooltip::ActorTooltip(Scene& ParentScene)
: ParentScene{ParentScene}
{
SDLWindow = SDL_CreateWindow(
"Tooltip",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
100,
100,
SDL_WINDOW_HIDDEN
| SDL_WINDOW_TOOLTIP
| SDL_WINDOW_BORDERLESS
| SDL_WINDOW_SKIP_TASKBAR
| SDL_WINDOW_ALWAYS_ON_TOP
);
CheckSDLError("Creating Tooltip Window");
}
Earlier in the project, we discussed the issue where, if our tooltip window is visible and positioned below our cursor, then that window will have mouse focus. This is a little annoying, and SDL2 doesn’t give us a platform-agnostic way to make our tooltip window non-focusable. So, we’ll just deal with it and work around it for now.
Alternatively, you can investigate ways to address this problem on your platform, if preferred.
We don’t cover platform-specific APIs in this course but, if we wanted to make our tooltip non-focusable in Windows, for example, the following is one option to make that happen:
// Editor/Source/ActorTooltip.cpp
#ifdef _WIN32
// Windows API
#include <windows.h>
// SDL system window manager hooks
#include <SDL_syswm.h>
#endif
#include "Editor/ActorTooltip.h"
#include "Editor/Scene.h"
using namespace Editor;
ActorTooltip::ActorTooltip(Scene& ParentScene)
: ParentScene{ParentScene}
{
SDLWindow = SDL_CreateWindow(
"Tooltip",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
100,
100,
SDL_WINDOW_HIDDEN
| SDL_WINDOW_TOOLTIP
| SDL_WINDOW_BORDERLESS
| SDL_WINDOW_SKIP_TASKBAR
| SDL_WINDOW_ALWAYS_ON_TOP
);
CheckSDLError("Creating Tooltip Window");
#ifdef _WIN32
SDL_SysWMinfo wmInfo;
SDL_GetVersion(&wmInfo.version);
SDL_GetWindowWMInfo(SDLWindow, &wmInfo);
HWND hwnd{wmInfo.info.win.window};
LONG style{GetWindowLong(hwnd, GWL_EXSTYLE)};
SetWindowLong(hwnd, GWL_EXSTYLE,
style |= WS_EX_NOACTIVATE
);
#endif
}
In version 3 of SDL, this is much easier, as an SDL_WINDOW_NOT_FOCUSABLE
flag has been added, alongside utilities like SDL_SetWindowFocusable()
. When we use these, SDL takes care of all the platform-specific implementation details for us behind the scenes.
// This does not exist in SDL2 - only SDL3
SDL_SetWindowFocusable(SomeWindow, false);
ActorTooltip
Destructor and Copy SemanticsJust like our main Editor::Window
, the ActorTooltip
class manages an SDL_Window
resource. Therefore, it needs a destructor (~ActorTooltip
) to ensure proper cleanup when an ActorTooltip
object goes out of scope or is destroyed.
In our program, we expect our Scene
and therefore our Tooltip
to be alive for the entire duration of our program, so this shouldn’t happen, but it’s etter to be proactive.
The destructor checks if SDL's video subsystem is still initialized using SDL_WasInit(SDL_INIT_VIDEO)
. If SDL has already shut down, the windows would have been cleaned up globally, so we don’t need to do anything.
Otherwise, if the SDLWindow
pointer is valid (not nullptr
), we explicitly destroy the tooltip window using SDL_DestroyWindow()
.
// Editor/Source/ActorTooltip.cpp
// ...
ActorTooltip::~ActorTooltip() {
if (!SDL_WasInit(SDL_INIT_VIDEO)) return;
if (SDLWindow) {
SDL_DestroyWindow(SDLWindow);
}
}
ActorTooltip
PositioningThe PositionWindow()
function is responsible for moving the tooltip window to follow the mouse cursor. We want the tooltip to appear right next to the cursor, creating the illusion that the user is dragging the actor image directly.
It uses SDL_GetGlobalMouseState()
to get the current mouse coordinates (x
, y
) relative to the entire screen, not just our application window. It then calls SDL_SetWindowPosition()
, passing the SDLWindow pointer and these global x
and y
coordinates, instantly repositioning the tooltip window to the cursor's location.
// Editor/Source/ActorTooltip.cpp
// ...
void ActorTooltip::PositionWindow() {
int x, y;
SDL_GetGlobalMouseState(&x, &y);
SDL_SetWindowPosition(SDLWindow, x, y);
}
ActorTooltip
VisibilityWe need a controlled way to show and hide the tooltip window. Creating and destroying windows frequently is inefficient, so toggling visibility is preferred.
The SetIsVisible()
method manages this state. It takes a boolean Visible
argument. It updates the internal isVisible
flag and then calls either SDL_ShowWindow()
if Visible
is true
, or SDL_HideWindow()
if Visible
is false
:
This synchronizes the internal state with the actual visibility of the SDL_Window
.
// Editor/Source/ActorTooltip.cpp
// ...
void ActorTooltip::SetIsVisible(bool Visible) {
isVisible = Visible;
if (isVisible) {
SDL_ShowWindow(SDLWindow);
} else {
SDL_HideWindow(SDLWindow);
}
}
ActorTooltip
Event HandlingThe ActorTooltip::HandleEvent()
method listens for the specific event that signals the start of a drag: our custom UserEvents::ACTOR_DRAG
.
When it receives an event of this type, it knows it needs to become active. It first calls SetIsVisible(true)
to make the tooltip window appear.
It then retrieves the pointer to the Actor
being dragged, which we stored in E.user.data1
when pushing the event. It casts this void*
back to an Actor*
and stores it in the DragActor
member variable for later use (e.g., rendering its image).
// Editor/Source/ActorTooltip.cpp
// ...
void ActorTooltip::HandleEvent(
const SDL_Event& E
) {
using namespace UserEvents;
if (E.type == ACTOR_DRAG) {
SetIsVisible(true);
DragActor = static_cast<Actor*>(
E.user.data1
);
}
}
ActorTooltip
TickingIn the Tick()
function, we handle the continuous aspects of the tooltip's behavior. The initial if (!isVisible) return;
ensures we only process logic when the tooltip should be active.
The main task is to determine if the drag should continue or end. We get the current mouse button states using SDL_GetGlobalMouseState()
. We test if the bit corresponding to the left mouse button (SDL_BUTTON_LEFT
) is set in the returned mask.
If it's not set, the user has let go, so we hide the tooltip using SetIsVisible(false)
. If the button is still held down, the drag continues, and we call PositionWindow()
to keep the tooltip aligned with the cursor.
// Editor/Source/ActorTooltip.cpp
// ...
void ActorTooltip::Tick(float DeltaTime) {
if (!isVisible) return;
auto Buttons{
SDL_GetGlobalMouseState(
nullptr, nullptr)};
if (!(Buttons & SDL_BUTTON_LEFT)) {
SetIsVisible(false);
} else {
PositionWindow();
}
}
ActorTooltip
RenderingThe ActorTooltip::Render()
function is responsible for drawing the content inside the tooltip window. For now, we haven't added the logic to draw the actor's image yet.
We'll implement the actual rendering in the next steps. At this stage, we provide an empty function body just so the code compiles. This allows us to integrate the ActorTooltip
into the Scene's render loop without causing errors, even though it won't visually draw anything yet.
// Editor/Source/ActorTooltip.cpp
// ...
void ActorTooltip::Render() {
// We'll implement this next...
}
To integrate our new tooltip system, we need to instantiate ActorTooltip
within the Editor::Scene
. We'll add it as a member variable and ensure its HandleEvent()
, Tick()
, and Render()
methods are called from the corresponding Scene
methods.
// Editor/Scene.h
// ...
#include "ActorTooltip.h"
namespace Editor{
class Scene {
// ...
void HandleEvent(const SDL_Event& E) {
ActorShelf.HandleEvent(E);
TooltipWindow.HandleEvent(E);
}
void Tick(float DeltaTime) {
ActorShelf.Tick(DeltaTime);
TooltipWindow.Tick(DeltaTime);
}
void Render(SDL_Surface* Surface) {
ActorShelf.Render(Surface);
TooltipWindow.Render();
}
private:
ActorTooltip TooltipWindow{*this};
// ...
};
}
Our tooltip window exists, appears, follows the mouse, and disappears correctly. Now we need to draw the actual actor image inside it. To do this, the ActorTooltip
needs access to information from the Actor instance it's currently tracking (DragActor
).
Specifically, the tooltip needs:
Image
object associated with the actor to render it.w
, h
) of the actor to size the tooltip window correctly.We'll add getter methods and a new member variable (DragOffset
) to the Actor
class to provide this information.
// Editor/Actor.h
#pragma once
#include <SDL.h>
#include "Image.h"
namespace Editor {
class Scene;
class Actor {
public:
// ...
const SDL_Rect& GetRect() const {
return Rect;
}
const SDL_Point& GetDragOffset() const {
return DragOffset;
}
const Image& GetArt() const {
return Art;
}
protected:
// ...
SDL_Point DragOffset{0, 0};
};
}
Our DragOffset
value let’s us remember exactly where on the actor the user clicked to start dragging. Remembering this position is not technically necessary, but we can use it to improve the user experience.
If we don’t store this offset, we can still position the tooltip next to the mouse, but its position will be slightly different to where the user started dragging. Perhaps we’d position the tooltip at the centre of the cursor, or the top left for example. Either way, it’s unlikely to match exactly where the user clicked:
But if we remember the drag offset, we can use that value to subtly adjust the position of our tooltip window, creating the effect of the actor "sticking" to the mouse more accurately.
When the user starts dragging our actor, we’ll calculate and store their mouse position relative to the top left of our actor’s rectangle:
// Editor/Source/Actor.cpp
// ...
void Actor::HandleEvent(const SDL_Event& E) {
if (
E.type == SDL_MOUSEBUTTONDOWN &&
E.button.button == SDL_BUTTON_LEFT &&
HasMouseFocus()
) {
DragOffset.x = E.button.x - Rect.x;
DragOffset.y = E.button.y - Rect.y;
SDL_Event DragEvent{UserEvents::ACTOR_DRAG};
DragEvent.user.data1 = this;
SDL_PushEvent(&DragEvent);
}
}
We’ll now modify ActorTooltip::PositionWindow()
to incorporate the DragOffset
. Before setting the window position, we retrieve the offset from the DragActor
using its new GetDragOffset()
method.
We then subtract the retrieved DragOffsetX
and DragOffsetY
from the global mouse coordinates (x
, y
) before passing them to SDL_SetWindowPosition()
. This adjustment ensures the tooltip window is positioned such that the original click point on the actor aligns with the current mouse cursor, creating the desired "sticky" dragging effect.
// Editor/Source/ActorTooltip.cpp
// ...
void ActorTooltip::PositionWindow() {
int x, y;
SDL_GetGlobalMouseState(&x, &y);
auto [DragOffsetX, DragOffsetY]{
DragActor->GetDragOffset()
};
SDL_SetWindowPosition(
SDLWindow,
x - DragOffsetX,
y - DragOffsetY
);
}
GetRect()
The tooltip window needs to be the same size as the actor being dragged. When the ACTOR_DRAG
event is handled in ActorTooltip::HandleEvent()
, right after identifying the DragActor
, we can now use its GetRect()
method.
We call SDL_SetWindowSize()
, passing it the SDLWindow
pointer. We extract the width (w
) and height (h
) from the SDL_Rect
returned by DragActor->GetRect()
and use these values as the new dimensions for the tooltip window.
This ensures the tooltip perfectly matches the size of the actor image it will display.
// Editor/Source/ActorTooltip.cpp
// ...
void ActorTooltip::HandleEvent(
const SDL_Event& E) {
using namespace UserEvents;
if (E.type == ACTOR_DRAG) {
DragActor = static_cast<Actor*>(
E.user.data1
);
SDL_SetWindowSize(
SDLWindow,
DragActor->GetRect().w,
DragActor->GetRect().h
);
SetIsVisible(true);
}
}
GetArt()
Finally, our Actor
's new GetArt()
method lets our tooltip retrieve the Image
associated with the actor being dragged. Let’s render that image within our tooltip.
Our image should take up the full size of our tooltip’s window surface, so we’ll render it at position (0
, 0
) with the width and height provided by the DragActor
's GetRect()
function.
In our case, that is also the same width and height as the tooltip window itself, as we set its size to these values when our drag event started.
Remember, to see our changes to a window surface, we need to call SDL_UpdateWindowSurface()
. In this case, we’re not going to be rendering anything else to our tooltip’s surface, so we can call this at the end of our Render()
function:
// Editor/Source/ActorTooltip.cpp
// ...
void ActorTooltip::Render() {
if (!isVisible) return;
DragActor->GetArt().Render(
GetSurface(),
SDL_Rect{
0, 0,
DragActor->GetRect().w,
DragActor->GetRect().h
});
SDL_UpdateWindowSurface(SDLWindow);
}
Running our program, we should now see that we can click and drag actors from our menu, and even drag them outside of our window:
Our new ActorTooltip
class is provided below:
#pragma once
#include <SDL.h>
#include "Actor.h"
namespace Editor{
class Scene;
class ActorTooltip {
public:
ActorTooltip(Scene& ParentScene);
~ActorTooltip();
ActorTooltip(const ActorTooltip&) = delete;
ActorTooltip& operator=(const ActorTooltip&)
= delete;
void Render();
void Tick(float DeltaTime);
void PositionWindow();
void HandleEvent(const SDL_Event& E);
void SetIsVisible(bool NewVisibility);
SDL_Surface* GetSurface() const {
return SDL_GetWindowSurface(SDLWindow);
}
private:
bool isVisible{false};
SDL_Window* SDLWindow{nullptr};
Actor* DragActor{nullptr};
Scene& ParentScene;
};
}
#include "Editor/ActorTooltip.h"
#include "Editor/Scene.h"
using namespace Editor;
ActorTooltip::ActorTooltip(Scene& ParentScene)
: ParentScene{ParentScene} {
SDLWindow = SDL_CreateWindow(
"Tooltip",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
100,
100,
SDL_WINDOW_HIDDEN
| SDL_WINDOW_TOOLTIP
| SDL_WINDOW_BORDERLESS
| SDL_WINDOW_SKIP_TASKBAR
| SDL_WINDOW_ALWAYS_ON_TOP
);
CheckSDLError("Creating Tooltip Window");
}
ActorTooltip::~ActorTooltip() {
if (!SDL_WasInit(SDL_INIT_VIDEO)) return;
if (SDLWindow) {
SDL_DestroyWindow(SDLWindow);
}
}
void ActorTooltip::Render() {
if (!isVisible) return;
DragActor->GetArt().Render(
GetSurface(),
SDL_Rect{
0, 0,
DragActor->GetRect().w,
DragActor->GetRect().h
});
SDL_UpdateWindowSurface(SDLWindow);
}
void ActorTooltip::Tick(float DeltaTime) {
if (!isVisible) return;
auto Buttons{
SDL_GetGlobalMouseState(
nullptr, nullptr)};
if (!(Buttons & SDL_BUTTON_LEFT)) {
SetIsVisible(false);
} else { PositionWindow(); }
}
void ActorTooltip::PositionWindow() {
int x, y;
SDL_GetGlobalMouseState(&x, &y);
auto [DragOffsetX, DragOffsetY]{
DragActor->GetDragOffset()
};
SDL_SetWindowPosition(
SDLWindow,
x - DragOffsetX,
y - DragOffsetY
);
}
void ActorTooltip::HandleEvent(
const SDL_Event& E) {
using namespace UserEvents;
if (E.type == ACTOR_DRAG) {
DragActor = static_cast<Actor*>(
E.user.data1
);
SDL_SetWindowSize(
SDLWindow,
DragActor->GetRect().w,
DragActor->GetRect().h
);
SetIsVisible(true);
}
}
void ActorTooltip::SetIsVisible(bool Visible) {
isVisible = Visible;
if (isVisible) {
SDL_ShowWindow(SDLWindow);
} else { SDL_HideWindow(SDLWindow); }
}
We also updated our Actor
class with some new capabilities, and added a new source file for implementing some of those functions:
#pragma once
#include <SDL.h>
#include "Image.h"
namespace Editor {
class Scene;
class Actor {
public:
Actor(
Scene& ParentScene,
const SDL_Rect& Rect,
Image& Image
) : ParentScene{ParentScene},
Rect{Rect},
Art{Image}
{}
bool HasMouseFocus() const;
virtual void HandleEvent(const SDL_Event& E);
void Tick(float DeltaTime) {}
void Render(SDL_Surface* Surface) {
Art.Render(Surface, Rect);
}
const SDL_Rect& GetRect() const {
return Rect;
}
const SDL_Point& GetDragOffset() const {
return DragOffset;
}
const Image& GetArt() const {
return Art;
}
protected:
Scene& ParentScene;
SDL_Rect Rect;
Image& Art;
SDL_Point DragOffset{0, 0};
};
}
#include "Editor/Actor.h"
#include "Editor/Scene.h"
using namespace Editor;
void Actor::HandleEvent(const SDL_Event& E) {
if (
E.type == SDL_MOUSEBUTTONDOWN &&
E.button.button == SDL_BUTTON_LEFT &&
HasMouseFocus()
) {
DragOffset.x = E.button.x - Rect.x;
DragOffset.y = E.button.y - Rect.y;
SDL_Event DragEvent{UserEvents::ACTOR_DRAG};
DragEvent.user.data1 = this;
SDL_PushEvent(&DragEvent);
}
}
bool Actor::HasMouseFocus() const {
if (!ParentScene.HasMouseFocus()) {
return false;
}
int x, y;
SDL_GetMouseState(&x, &y);
if (
x < Rect.x ||
x > Rect.x + Rect.w ||
y < Rect.y ||
y > Rect.y + Rect.h
) { return false; }
return true;
}
We updated our Scene
to add an instance of our ActorTooltip
, and we updated our Config.h
to register a custom event type:
#pragma once
#include <SDL.h>
#include "ActorMenu.h"
#include "ActorTooltip.h"
#include "AssetManager.h"
#include "Window.h"
namespace Editor{
class Scene {
public:
Scene(Window& ParentWindow)
: ParentWindow{ParentWindow}
{}
void HandleEvent(const SDL_Event& E) {
ActorShelf.HandleEvent(E);
TooltipWindow.HandleEvent(E);
}
void Tick(float DeltaTime) {
ActorShelf.Tick(DeltaTime);
TooltipWindow.Tick(DeltaTime);
}
void Render(SDL_Surface* Surface) {
ActorShelf.Render(Surface);
TooltipWindow.Render();
}
AssetManager& GetAssets() {
return Assets;
}
bool HasMouseFocus() const {
return ParentWindow.HasMouseFocus();
}
Window& GetWindow() const {
return ParentWindow;
}
private:
ActorMenu ActorShelf{*this};
ActorTooltip TooltipWindow{*this};
Window& ParentWindow;
AssetManager Assets;
};
}
#pragma once
#include <iostream>
#include <SDL.h>
#include <string>
#include <vector>
namespace UserEvents{
#ifdef WITH_EDITOR
inline Uint32 ACTOR_DRAG{
SDL_RegisterEvents(1)};
#endif
}
namespace Config {
inline const std::vector BUTTON_COLORS{
SDL_Color{15, 15, 15, 255}, // Normal
SDL_Color{15, 155, 15, 255}, // Hover
SDL_Color{225, 15, 15, 255}, // Active
SDL_Color{60, 60, 60, 255} // Disabled
};
inline constexpr SDL_Color FONT_COLOR{
255, 255, 255, 255};
inline const std::string FONT{
"Assets/Rubik-SemiBold.ttf"};
}
#ifdef WITH_EDITOR
namespace Config::Editor {
// Window
inline const std::string WINDOW_TITLE{"Editor"};
inline const int WINDOW_WIDTH{730};
inline const int WINDOW_HEIGHT{300};
inline const SDL_Color WINDOW_BACKGROUND{
35, 35, 35, 255};
// ActorMenu
inline const int ACTOR_MENU_WIDTH{70};
inline const int ACTOR_MENU_POSITION_X{
WINDOW_WIDTH - ACTOR_MENU_WIDTH};
inline const SDL_Color ACTOR_MENU_BACKGROUND{
15, 15, 15, 255};
inline const int PADDING{10};
}
#endif
inline void CheckSDLError(
const std::string& Msg) {
#ifdef CHECK_ERRORS
const char* error = SDL_GetError();
if (*error != '\0') {
std::cerr << Msg << " Error: "
<< error << '\n';
SDL_ClearError();
}
#endif
}
This lesson focused on making the ActorMenu
interactive by adding drag capabilities. We enabled users to click on an actor, triggering a visual feedback mechanism.
The core components added were a custom ACTOR_DRAG
event and the ActorTooltip
class. The Actor
now detects clicks and pushes this event, carrying a pointer to itself.
The ActorTooltip
listens for this event, creates a borderless, always-on-top window, sizes it appropriately, and displays the dragged actor's image. This tooltip window dynamically follows the global mouse cursor until the mouse button is released.
We reviewed how to:
SDL_WindowFlags
to create specialized windows (tooltips).Implement clicking and dragging actors from the menu, showing a tooltip that follows the mouse cursor.
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games