Below is code for handling events in SFML. It works by reading a text file containing a sequence of functions to be called when a given event is detected:
EventManager.hpp
#pragma once
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <vector>
#include <unordered_map>
#include <functional>
#include <fstream>
#include <sstream>
#include <iostream>
namespace AGE
{
enum class EventType
{
KeyDown = sf::Event::KeyPressed,
KeyUp = sf::Event::KeyReleased,
MButtonDown = sf::Event::MouseButtonPressed,
MButtonUp = sf::Event::MouseButtonReleased,
MouseWheel = sf::Event::MouseWheelMoved,
WindowResized = sf::Event::Resized,
GainedFocus = sf::Event::GainedFocus,
LostFocus = sf::Event::LostFocus,
MouseEntered = sf::Event::MouseEntered,
MouseLeft = sf::Event::MouseLeft,
Closed = sf::Event::Closed,
TextEntered = sf::Event::TextEntered,
Keyboard = sf::Event::Count + 1, Mouse, Joystick
};
struct EventInfo
{
EventInfo()
{
this->code = 0;
}
EventInfo(int code)
{
this->code = code;
}
int code;
};
using Events = std::vector<std::pair<EventType, EventInfo>>;
struct EventDetails
{
EventDetails(const std::string& binding_name)
{
this->binding_name = binding_name;
Clear();
}
void Clear()
{
this->size = { 0, 0 };
this->text_entered = { 0 };
this->mouse = { 0, 0 };
this->mouse_wheel_delta = { 0 };
this->key_code = { -1 };
}
std::string binding_name;
sf::Vector2i size;
sf::Uint32 text_entered;
sf::Vector2i mouse;
int mouse_wheel_delta;
int key_code;
};
struct Binding
{
Binding(const std::string& name) :
details(name),
name(name),
event_count(0)
{}
void BindEvent(EventType type, EventInfo info = EventInfo())
{
this->events.emplace_back(type, info);
}
EventDetails details;
Events events;
std::string name;
int event_count;
};
using Bindings = std::unordered_map<std::string, Binding*>;
using Callbacks = std::unordered_map<std::string, std::function<void(EventDetails*)>>;
class EventManager
{
public:
EventManager();
~EventManager();
bool AddBinding(Binding* binding);
bool RemoveBinding(const std::string& name);
void HandleEvent(sf::Event& event);
void Update();
void SetFocus(const bool focus);
template <class T>
bool AddCallback(const std::string& name,
void(T::*function)(EventDetails*),
T* class_instance)
{
auto func = std::bind(function, class_instance, std::placeholders::_1);
return this->callbacks.emplace(name, func).second;
}
void RemoveCallback(const std::string& name)
{
this->callbacks.erase(name);
}
private:
void LoadBindings();
private:
Callbacks callbacks;
Bindings bindings;
bool has_focus;
};
}
EventManager.cpp
#include "EventManager.hpp"
namespace AGE
{
EventManager::EventManager() :
has_focus(true)
{
LoadBindings();
}
EventManager::~EventManager()
{
for (auto& itr : this->bindings)
{
delete itr.second;
itr.second = nullptr;
}
}
bool EventManager::AddBinding(Binding* binding)
{
if (this->bindings.find(binding->name) != this->bindings.end())
{
return false;
}
return this->bindings.emplace(binding->name, binding).second;
}
bool EventManager::RemoveBinding(const std::string& name)
{
auto itr = this->bindings.find(name);
if (itr != this->bindings.end())
{
delete itr->second;
this->bindings.erase(itr);
return true;
}
return false;
}
void EventManager::HandleEvent(sf::Event& event)
{
for (auto& b_itr : this->bindings)
{
Binding* binding = b_itr.second;
for (auto& e_itr : binding->events)
{
EventType event_type = static_cast<EventType>(event.type);
if (e_itr.first != event_type) { continue; };
if ((event_type == EventType::KeyDown) || (event_type == EventType::KeyUp))
{
if (e_itr.second.code == event.key.code)
{
++(binding->event_count);
break;
}
}
else if ((event_type == EventType::MButtonDown) || (event_type == EventType::MButtonUp))
{
if (e_itr.second.code == event.mouseButton.button)
{
binding->details.mouse.x = event.mouseButton.x;
binding->details.mouse.y = event.mouseButton.y;
++(binding->event_count);
break;
}
}
else
{
if (event_type == EventType::MouseWheel)
{
binding->details.mouse_wheel_delta = event.mouseWheel.delta;
}
else if (event_type == EventType::WindowResized)
{
binding->details.size.x = event.size.width;
binding->details.size.y = event.size.height;
}
else if (event_type == EventType::TextEntered)
{
binding->details.text_entered = event.text.unicode;
}
++(binding->event_count);
}
}
}
}
void EventManager::Update()
{
if (!this->has_focus) { return; };
for (auto& b_itr : this->bindings)
{
Binding* binding = b_itr.second;
for (auto& e_itr : binding->events)
{
switch (e_itr.first)
{
default:
break;
case EventType::Keyboard:
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key(e_itr.second.code)))
{
++(binding->event_count);
}
break;
case EventType::Mouse:
if (sf::Mouse::isButtonPressed(sf::Mouse::Button(e_itr.second.code)))
{
++(binding->event_count);
}
break;
}
if (binding->events.size() == binding->event_count)
{
auto callback_itr = this->callbacks.find(binding->name);
if (callback_itr != this->callbacks.end())
{
callback_itr->second(&binding->details);
}
}
binding->event_count = 0;
binding->details.Clear();
}
}
}
void EventManager::SetFocus(const bool focus)
{
this->has_focus = focus;
}
void EventManager::LoadBindings()
{
const std::string deliminator = ":";
std::ifstream bindings;
bindings.open("C:/dev/MyVSProjects/C++/Racer/Racer/Racer/src/Keys.cfg");
if (!bindings.is_open())
{
std::cerr << "Failed to open key file!" << std::endl;
return;
}
std::string line;
while (std::getline(bindings, line))
{
std::stringstream key_stream(line);
std::string callback_name;
key_stream >> callback_name;
Binding* binding = new Binding(callback_name);
while (!key_stream.eof())
{
std::string key_value;
key_stream >> key_value;
int start = 0;
int end = key_value.find(deliminator);
if (end == std::string::npos)
{
delete binding;
binding = nullptr;
break;
}
EventType type = EventType(std::stoi(key_value.substr(start, end - start)));
int code = std::stoi(key_value.substr(end + deliminator.length(), key_value.find(deliminator, end + deliminator.length())));
EventInfo event_info;
event_info.code = code;
binding->BindEvent(type, event_info);
}
if (!AddBinding(binding)) { delete binding; };
}
bindings.close();
}
}
Short example: (Keys.cfg then tells the code to call function "cout" when keybutton 89 (f5) is pressed)
Keys.cfg
COUT 5:89
Main.cpp
#include <SFML/Graphics.hpp>
#include "EventManager.hpp"
#include <iostream>
class Test
{
public:
Test() = default;
void COUT(AGE::EventDetails* details)
{
std::cout << "Test" << std::endl;
}
};
int main()
{
Test test;
sf::RenderWindow window(sf::VideoMode(800, 600), "Test");
AGE::EventManager event_manager;
event_manager.AddCallback("COUT", &Test::COUT, &test);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::LostFocus)
{
event_manager.SetFocus(false);
}
else if (event.type == sf::Event::GainedFocus)
{
event_manager.SetFocus(true);
}
event_manager.HandleEvent(event);
}
event_manager.Update();
window.clear();
window.display();
}
}
I would like any tips to improve the code in any way possible. Looking to learn!