Gun refactorings + preliminary aim cursor

This commit is contained in:
Fabrice CARUSO
2022-05-26 01:54:15 +02:00
parent 4108cac898
commit 5457946fca
10 changed files with 390 additions and 226 deletions
+6 -1
View File
@@ -304,7 +304,6 @@ set(COMMON_LIBRARIES
${FREETYPE_LIBRARIES}
${FreeImage_LIBRARIES}
${SDL2_LIBRARY}
${UDEV_LIBRARY}
# batocera / background musics
${SDLMIXER_LIBRARY}
# batocera / customisations
@@ -316,6 +315,12 @@ set(COMMON_LIBRARIES
libcheevos
)
if(UDEV_FOUND)
LIST(APPEND COMMON_LIBRARIES
${UDEV_LIBRARY}
)
endif()
if(ENABLE_PULSE)
LIST(APPEND COMMON_LIBRARIES
${PULSEAUDIO_LIBRARY}
+3 -9
View File
@@ -668,7 +668,7 @@ int main(int argc, char* argv[])
while(SDL_PollEvent(&event));
// check guns
InputManager::getInstance()->parseGuns(&window);
InputManager::getInstance()->updateGuns(&window);
// triggered if exiting from SDL_WaitEvent due to event
if (ps_standby)
@@ -685,17 +685,11 @@ int main(int argc, char* argv[])
// ps_time = SDL_GetTicks();
// check guns
InputManager::getInstance()->parseGuns(&window);
InputManager::getInstance()->updateGuns(&window);
}
// just to test
float perx, pery;
for (auto iter = InputManager::getInstance()->getGuns().begin(); iter != InputManager::getInstance()->getGuns().end(); iter++) {
InputManager::getInstance()->getGunPosition(iter->second, perx, pery);
printf("gun %i : %.2f %.2f\n", iter->first, perx, pery);
}
if(window.isSleeping())
if (window.isSleeping())
{
lastTime = SDL_GetTicks();
SDL_Delay(1); // this doesn't need to be accurate, we're just giving up our CPU time until something wakes us up
+2
View File
@@ -10,6 +10,7 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.h
${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/GunManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Log.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MameNames.h
${CMAKE_CURRENT_SOURCE_DIR}/src/gettext.h # batocera
@@ -126,6 +127,7 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GunManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Log.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MameNames.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/LocaleES.cpp # batocera
+244
View File
@@ -0,0 +1,244 @@
#include "GunManager.h"
#include "Log.h"
#include "platform.h"
#include "Window.h"
#include <SDL.h>
#include <iostream>
#include <assert.h>
#include <algorithm>
#include "utils/StringUtil.h"
#include "LocaleES.h"
#ifdef HAVE_UDEV
#include <poll.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <unistd.h>
#define test_bit(array, bit) (array[bit/8] & (1<<(bit%8)))
#endif
#if WIN32
#include <Windows.h>
#endif
GunManager::GunManager()
{
#ifdef HAVE_UDEV
udev = udev_new();
if (udev != NULL)
{
udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (udev_monitor)
{
udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "input", NULL);
udev_monitor_enable_receiving(udev_monitor);
}
}
udev_initial_gunsList();
#endif
}
GunManager::~GunManager()
{
#ifdef HAVE_UDEV
if (udev != NULL)
{
// close guns
for (auto gun : mGuns)
{
udev_removeGun(gun->dev, NULL);
delete gun;
}
mGuns.clear();
if (udev_monitor)
udev_monitor_unref(udev_monitor);
udev_unref(udev);
}
#endif
}
void GunManager::updateGuns(Window* window)
{
#ifdef HAVE_UDEV
const char* val_gun;
const char* action;
while (udev_monitor && udev_input_poll_hotplug_available(udev_monitor))
{
struct udev_device *dev = udev_monitor_receive_device(udev_monitor);
bool dev_handled = false;
if (dev != NULL)
{
val_gun = udev_device_get_property_value(dev, "ID_INPUT_GUN"); // ID_INPUT_GUN and ID_INPUT_MOUSE for some tests
action = udev_device_get_action(dev);
if (val_gun != NULL && strncmp(val_gun, "1", 1) == 0)
{
if (strncmp(action, "add", 3) == 0)
{
if (udev_addGun(dev, window))
dev_handled = true;
}
else if (strncmp(action, "remove", 6) == 0)
{
if (udev_removeGun(dev, window))
dev_handled = true;
}
}
if (!dev_handled)
udev_device_unref(dev); // not handled, clean it
}
}
#elif WIN32
if (mGuns.size() == 0)
{
Gun* newgun = new Gun();
newgun->mIndex = mGuns.size();
newgun->mName = "Mouse";
mGuns.push_back(newgun);
}
#endif
for (Gun* gun : mGuns)
updateGunPosition(gun);
}
bool GunManager::updateGunPosition(Gun* gun)
{
#ifdef HAVE_UDEV
struct input_absinfo absinfo;
if (ioctl(gun->fd, EVIOCGABS(ABS_X), &absinfo) == -1)
return false;
gun->mX = ((float)(absinfo.value - absinfo.minimum)) / ((float)(absinfo.maximum - absinfo.minimum));
if (ioctl(gun->fd, EVIOCGABS(ABS_Y), &absinfo) == -1)
return false;
gun->mY = ((float)(absinfo.value - absinfo.minimum)) / ((float)(absinfo.maximum - absinfo.minimum));
return true;
#elif WIN32
int x, y;
SDL_GetMouseState(&x, &y);
gun->mX = x;
gun->mY = y;
return true;
#endif
return false;
}
#ifdef HAVE_UDEV
// function from retroarch
bool GunManager::udev_input_poll_hotplug_available(struct udev_monitor *dev)
{
struct pollfd fds;
fds.fd = udev_monitor_get_fd(dev);
fds.events = POLLIN;
fds.revents = 0;
return (poll(&fds, 1, 0) == 1) && (fds.revents & POLLIN);
}
void GunManager::udev_initial_gunsList()
{
struct udev_list_entry *devs = NULL;
struct udev_list_entry *item = NULL;
struct udev_enumerate *enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_property(enumerate, "ID_INPUT_GUN", "1");
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_scan_devices(enumerate);
devs = udev_enumerate_get_list_entry(enumerate);
for (item = devs; item; item = udev_list_entry_get_next(item))
{
const char *name = udev_list_entry_get_name(item);
struct udev_device *dev = udev_device_new_from_syspath(udev, name);
if (udev_addGun(dev, NULL) == false) udev_device_unref(dev); // unhandled device
}
}
bool GunManager::udev_addGun(struct udev_device *dev, Window* window)
{
char ident[256];
const char* devnode;
unsigned char abscaps[(ABS_MAX / 8) + 1] = { '\0' };
// check that devnode is of form /dev/input/eventXX (to not include devices aliases)
devnode = udev_device_get_devnode(dev);
if (devnode == NULL) return false;
if (strncmp(devnode, "/dev/input/event", 16) != 0) return false;
int fd = open(devnode, O_RDONLY | O_NONBLOCK);
if (fd < 0) return false;
// check absolute capabilities
if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abscaps)), abscaps) == -1) {
close(fd);
return false;
}
// check absolute capabilities x and y
if (((test_bit(abscaps, ABS_X)) && (test_bit(abscaps, ABS_Y))) == false) {
close(fd);
return false;
}
LOG(LogInfo) << "Gun found at " << devnode;
if (ioctl(fd, EVIOCGNAME(sizeof(ident)), ident) < 0)
ident[0] = '\0';
Gun* newgun = new Gun();
newgun->mIndex = mGuns.size();
newgun->mName = std::string(ident);
newgun->devpath = devnode;
newgun->dev = dev;
newgun->fd = fd;
if (!newgun->mName.empty() && window != NULL)
window->displayNotificationMessage(_U("\uF05B ") + Utils::String::format(_("%s connected").c_str(), Utils::String::trim(newgun->mName).c_str()));
mGuns.push_back(newgun);
return false;
}
bool GunManager::udev_removeGun(struct udev_device *dev, Window* window)
{
const char* devnode;
LOG(LogInfo) << "Gun removed at " << devnode;
devnode = udev_device_get_devnode(dev);
if (devnode == NULL) return false;
for (auto iter = mGuns.begin(); iter != mGuns.end(); iter++)
{
if (iter->devpath == devnode)
{
LOG(LogInfo) << "Gun removed found at " << devnode;
if (window != NULL)
window->displayNotificationMessage(_U("\uF05B ") + Utils::String::format(_("%s disconnected").c_str(), Utils::String::trim(iter->mName).c_str()));
close(iter->fd);
udev_device_unref(iter->dev);
delete *iter;
mGuns.erase(iter);
return true;
}
}
return false;
}
#endif
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#ifndef ES_CORE_GUNMANAGER_H
#define ES_CORE_GUNMANAGER_H
#include <string>
#include <vector>
#ifdef HAVE_UDEV
#include <libudev.h>
#endif
class InputConfig;
class Window;
union SDL_Event;
class Gun
{
friend class GunManager;
public:
Gun()
{
mIndex = 0;
mX = -1;
mY = -1;
}
int index() { return mIndex; }
std::string& name() { return mName; }
float x() { return mX; }
float y() { return mY; }
private:
std::string mName;
float mX;
float mY;
int mIndex;
#ifdef HAVE_UDEV
std::string devpath;
udev_device* dev;
int fd;
#endif
};
//you should only ever instantiate one of these, by the way
class GunManager
{
public:
GunManager();
virtual ~GunManager();
std::vector<Gun*>& getGuns() { return mGuns; }
void updateGuns(Window* window);
private:
bool updateGunPosition(Gun* gun);
std::vector<Gun*> mGuns;
#ifdef HAVE_UDEV
struct udev *udev;
struct udev_monitor *udev_monitor;
static bool udev_input_poll_hotplug_available(struct udev_monitor *dev);
void udev_initial_gunsList();
bool udev_addGun(struct udev_device *dev, Window* window);
bool udev_removeGun(struct udev_device *dev, Window* window);
#endif
};
#endif // ES_CORE_GUNMANAGER_H
+13 -180
View File
@@ -16,15 +16,7 @@
#include "utils/StringUtil.h"
#include "LocaleES.h"
#include "Paths.h"
#ifdef HAVE_UDEV
#include <poll.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <unistd.h>
#define test_bit(array, bit) (array[bit/8] & (1<<(bit%8)))
#endif
#include "GunManager.h"
#define KEYBOARD_GUID_STRING "-1"
#define CEC_GUID_STRING "-2"
@@ -60,7 +52,7 @@ InputManager::~InputManager()
InputManager* InputManager::getInstance()
{
if(!mInstance)
if (!mInstance)
mInstance = new InputManager();
return mInstance;
@@ -87,17 +79,7 @@ void InputManager::init()
mMouseButtonsInputConfig->mapInput(BUTTON_OK, Input(DEVICE_MOUSE, TYPE_BUTTON, 1, 1, true));
mMouseButtonsInputConfig->mapInput(BUTTON_BACK, Input(DEVICE_MOUSE, TYPE_BUTTON, 3, 1, true));
#ifdef HAVE_UDEV
udev = udev_new();
if(udev != NULL) {
udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (udev_monitor){
udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "input", NULL);
udev_monitor_enable_receiving(udev_monitor);
}
}
udev_initial_gunsList();
#endif
mGunManager = new GunManager();
}
void InputManager::deinit()
@@ -130,20 +112,11 @@ void InputManager::deinit()
SDL_JoystickEventState(SDL_DISABLE);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
#ifdef HAVE_UDEV
if(udev != NULL) {
// close guns
for (auto iter = mGuns.begin(); iter != mGuns.end(); iter++) {
udev_removeGun(iter->second->dev, NULL);
delete iter->second;
}
mGuns.clear();
if (udev_monitor) {
udev_monitor_unref(udev_monitor);
}
udev_unref(udev);
if (mGunManager != nullptr)
{
delete mGunManager;
mGunManager = nullptr;
}
#endif
}
int InputManager::getNumJoysticks()
@@ -256,152 +229,6 @@ void InputManager::rebuildAllJoysticks(bool deinit)
SDL_JoystickEventState(SDL_ENABLE);
}
#ifdef HAVE_UDEV
// function from retroarch
bool InputManager::udev_input_poll_hotplug_available(struct udev_monitor *dev)
{
struct pollfd fds;
fds.fd = udev_monitor_get_fd(dev);
fds.events = POLLIN;
fds.revents = 0;
return (poll(&fds, 1, 0) == 1) && (fds.revents & POLLIN);
}
void InputManager::udev_initial_gunsList() {
struct udev_list_entry *devs = NULL;
struct udev_list_entry *item = NULL;
struct udev_enumerate *enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_property(enumerate, "ID_INPUT_GUN", "1");
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_scan_devices(enumerate);
devs = udev_enumerate_get_list_entry(enumerate);
for (item = devs; item; item = udev_list_entry_get_next(item)) {
const char *name = udev_list_entry_get_name(item);
struct udev_device *dev = udev_device_new_from_syspath(udev, name);
if(udev_addGun(dev, NULL) == false) udev_device_unref(dev); // unhandled device
}
}
bool InputManager::udev_addGun(struct udev_device *dev, Window* window) {
char ident[256];
const char* devnode;
unsigned char abscaps[(ABS_MAX / 8) + 1] = {'\0'};
// check that devnode is of form /dev/input/eventXX (to not include devices aliases)
devnode = udev_device_get_devnode(dev);
if(devnode == NULL) return false;
if(strncmp(devnode, "/dev/input/event", 16) != 0) return false;
int fd = open(devnode, O_RDONLY | O_NONBLOCK);
if(fd < 0) return false;
// check absolute capabilities
if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abscaps)), abscaps) == -1) {
close(fd);
return false;
}
// check absolute capabilities x and y
if( ( (test_bit(abscaps, ABS_X)) && (test_bit(abscaps, ABS_Y)) ) == false) {
close(fd);
return false;
}
LOG(LogInfo) << "Gun found at " << devnode;
if (ioctl(fd, EVIOCGNAME(sizeof(ident)), ident) < 0)
ident[0] = '\0';
Gun* newgun = new Gun();
newgun->name = std::string(ident);
newgun->devpath = devnode;
newgun->dev = dev;
newgun->fd = fd;
if (!newgun->name.empty() && window != NULL)
window->displayNotificationMessage(_U("\uF05B ") + Utils::String::format(_("%s connected").c_str(), Utils::String::trim(newgun->name).c_str()));
mGuns[mGuns.size()] = newgun;
return false;
}
bool InputManager::udev_removeGun(struct udev_device *dev, Window* window) {
const char* devnode;
LOG(LogInfo) << "Gun removed at " << devnode;
devnode = udev_device_get_devnode(dev);
if(devnode == NULL) return false;
for (auto iter = mGuns.begin(); iter != mGuns.end(); iter++) {
if(iter->second->devpath == devnode) {
LOG(LogInfo) << "Gun removed found at " << devnode;
if(window != NULL)
window->displayNotificationMessage(_U("\uF05B ") + Utils::String::format(_("%s disconnected").c_str(), Utils::String::trim(iter->second->name).c_str()));
close(iter->second->fd);
udev_device_unref(iter->second->dev);
delete iter->second;
mGuns.erase(iter);
return true;
}
}
return false;
}
#endif
void InputManager::parseGuns(Window* window) {
#ifdef HAVE_UDEV
const char* val_gun;
const char* action;
while (udev_monitor && udev_input_poll_hotplug_available(udev_monitor)) {
struct udev_device *dev = udev_monitor_receive_device(udev_monitor);
bool dev_handled = false;
if(dev != NULL) {
val_gun = udev_device_get_property_value(dev, "ID_INPUT_GUN"); // ID_INPUT_GUN and ID_INPUT_MOUSE for some tests
action = udev_device_get_action(dev);
if(val_gun != NULL && strncmp(val_gun, "1", 1) == 0) {
if(strncmp(action, "add", 3) == 0) {
if(udev_addGun(dev, window)) dev_handled = true;
} else if(strncmp(action, "remove", 6) == 0) {
if(udev_removeGun(dev, window)) dev_handled = true;
}
}
if(dev_handled == false) udev_device_unref(dev); // not handled, clean it
}
}
#endif
}
bool InputManager::getGunPosition(Gun* gun, float& perx, float& pery) {
#ifdef HAVE_UDEV
struct input_absinfo absinfo;
if (ioctl(gun->fd, EVIOCGABS(ABS_X), &absinfo) == -1) {
return false;
}
perx = ((float)(absinfo.value - absinfo.minimum)) / ((float)(absinfo.maximum - absinfo.minimum));
if (ioctl(gun->fd, EVIOCGABS(ABS_Y), &absinfo) == -1) {
return false;
}
pery = ((float)(absinfo.value - absinfo.minimum)) / ((float)(absinfo.maximum - absinfo.minimum));
return true;
#endif
return false;
}
bool InputManager::parseEvent(const SDL_Event& ev, Window* window)
{
bool causedEvent = false;
@@ -965,3 +792,9 @@ std::vector<InputConfig*> InputManager::getInputConfigs()
return ret;
}
void InputManager::updateGuns(Window* window)
{
if (mGunManager)
mGunManager->updateGuns(window);
}
+9 -27
View File
@@ -7,12 +7,12 @@
#include <pugixml/src/pugixml.hpp>
#include <utils/Delegate.h>
#ifdef HAVE_UDEV
#include <libudev.h>
#endif
#include "GunManager.h"
class InputConfig;
class Window;
class GunManager;
union SDL_Event;
struct PlayerDeviceInfo
@@ -27,15 +27,6 @@ public:
virtual void onJoystickChanged() = 0;
};
struct Gun {
std::string name;
#ifdef HAVE_UDEV
std::string devpath;
udev_device* dev;
int fd;
#endif
};
//you should only ever instantiate one of these, by the way
class InputManager
{
@@ -58,9 +49,8 @@ public:
bool parseEvent(const SDL_Event& ev, Window* window);
void parseGuns(Window* window);
bool getGunPosition(Gun* gun, float& perx, float& pery);
std::map<int, Gun*>& getGuns() { return mGuns; }
void updateGuns(Window* window);
std::vector<Gun*>& getGuns() { return mGunManager->getGuns(); }
std::string configureEmulators();
@@ -72,9 +62,13 @@ public:
static Delegate<IJoystickChangedEvent> joystickChanged;
GunManager* getGunManager() { return mGunManager; }
private:
InputManager();
GunManager* mGunManager;
static InputManager* mInstance;
static const int DEADZONE = 23000;
static std::string getTemporaryConfigPath();
@@ -85,8 +79,6 @@ private:
std::map<SDL_JoystickID, SDL_Joystick*> mJoysticks;
std::map<SDL_JoystickID, InputConfig*> mInputConfigs;
std::map<int, Gun*> mGuns;
InputConfig* mMouseButtonsInputConfig;
InputConfig* mKeyboardInputConfig;
InputConfig* mCECInputConfig;
@@ -104,16 +96,6 @@ private:
void clearJoysticks();
void rebuildAllJoysticks(bool deinit = true);
#ifdef HAVE_UDEV
struct udev *udev;
struct udev_monitor *udev_monitor;
static bool udev_input_poll_hotplug_available(struct udev_monitor *dev);
void udev_initial_gunsList();
bool udev_addGun(struct udev_device *dev, Window* window);
bool udev_removeGun(struct udev_device *dev, Window* window);
#endif
};
#endif // ES_CORE_INPUT_MANAGER_H
+36 -9
View File
@@ -502,13 +502,13 @@ void Window::render()
mRenderedHelpPrompts = false;
// draw only bottom and top of GuiStack (if they are different)
if(mGuiStack.size())
if (mGuiStack.size())
{
auto& bottom = mGuiStack.front();
auto& top = mGuiStack.back();
bottom->render(transform);
if(bottom != top)
if (bottom != top)
{
if ((top->getTag() == "GuiLoading") && mGuiStack.size() > 2)
{
@@ -534,21 +534,21 @@ void Window::render()
}
}
}
if (mGuiStack.size() < 2 || !Renderer::isSmallScreen())
if(!mRenderedHelpPrompts)
if (!mRenderedHelpPrompts)
mHelp->render(transform);
if(Settings::DrawFramerate() && mFrameDataText)
if (Settings::DrawFramerate() && mFrameDataText)
{
Renderer::setMatrix(Transform4x4f::Identity());
mDefaultFonts.at(1)->renderTextCache(mFrameDataText.get());
}
// clock
// clock
if (Settings::DrawClock() && mClock && (mGuiStack.size() < 2 || !Renderer::isSmallScreen()))
mClock->render(transform);
if (Settings::ShowControllerActivity() && mControllerActivity != nullptr && (mGuiStack.size() < 2 || !Renderer::isSmallScreen()))
mControllerActivity->render(transform);
@@ -569,7 +569,7 @@ void Window::render()
renderAsyncNotifications(transform);
}
// Always call the screensaver render function regardless of whether the screensaver is active
// or not because it may perform a fade on transition
renderScreenSaver();
@@ -580,7 +580,7 @@ void Window::render()
if (mVolumeInfo && Settings::VolumePopup())
mVolumeInfo->render(transform);
if(mTimeSinceLastInput >= screensaverTime && screensaverTime != 0)
if (mTimeSinceLastInput >= screensaverTime && screensaverTime != 0)
{
if (!isProcessing() && mAllowSleep && (!mScreenSaver || mScreenSaver->allowSleep()))
{
@@ -591,6 +591,33 @@ void Window::render()
}
}
}
// just to test
for (auto gun : InputManager::getInstance()->getGuns())
{
if (mGunAimTexture == nullptr)
mGunAimTexture = TextureResource::get(":/gun.png", false, false, true, false);
if (mGunAimTexture->bind())
{
int pointerSize = 24;
const Vector2f topLeft = { gun->x() - pointerSize, gun->y() - pointerSize };
const Vector2f bottomRight = { gun->x() + pointerSize, gun->y() + pointerSize };
Renderer::Vertex vertices[4];
vertices[0] = { { topLeft.x() , topLeft.y() }, { 0.0f, 0.0f }, 0xFFFFFFFF };
vertices[1] = { { topLeft.x() , bottomRight.y() }, { 0.0f, 1.0f }, 0xFFFFFFFF };
vertices[2] = { { bottomRight.x(), topLeft.y() }, { 1.0f, 0.0f }, 0xFFFFFFFF };
vertices[3] = { { bottomRight.x(), bottomRight.y() }, { 1.0f, 1.0f }, 0xFFFFFFFF };
Renderer::drawTriangleStrips(&vertices[0], 4);
}
}
// auto output = Utils::String::format("gun %i : %.2f %.2f\n", gun->index(), gun->x(), gun->y());
}
void Window::normalizeNextUpdate()
+2
View File
@@ -159,6 +159,8 @@ private:
bool mRenderedHelpPrompts;
int mTransitionOffset;
std::shared_ptr<TextureResource> mGunAimTexture;
};
#endif // ES_CORE_WINDOW_H
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB