@ -0,0 +1,124 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_APP_DISPLAY_HPP | |||||
#define ANTKEEPER_APP_DISPLAY_HPP | |||||
#include "math/vector.hpp" | |||||
#include <string> | |||||
namespace app { | |||||
/** | |||||
* Virtual display. | |||||
*/ | |||||
class display | |||||
{ | |||||
public: | |||||
/** | |||||
* Sets the index of the display. | |||||
* | |||||
* @param index Index of the display. | |||||
*/ | |||||
inline void set_index(int index) noexcept | |||||
{ | |||||
this->index = index; | |||||
} | |||||
/** | |||||
* Sets the name of the display. | |||||
* | |||||
* @param name Name of the display. | |||||
*/ | |||||
inline void set_name(const std::string& name) noexcept | |||||
{ | |||||
this->name = name; | |||||
} | |||||
/** | |||||
* Sets the size of the display. | |||||
* | |||||
* @param size Size of the display, in display units. | |||||
*/ | |||||
inline void set_size(const math::vector<int, 2>& size) noexcept | |||||
{ | |||||
this->size = size; | |||||
} | |||||
/** | |||||
* Sets the refresh rate of the display. | |||||
* | |||||
* @param rate Refresh rate, in Hz. | |||||
*/ | |||||
inline void set_refresh_rate(int rate) noexcept | |||||
{ | |||||
this->refresh_rate = rate; | |||||
} | |||||
/** | |||||
* Sets the DPI of the display. | |||||
* | |||||
* @param dpi DPI. | |||||
*/ | |||||
inline void set_dpi(float dpi) noexcept | |||||
{ | |||||
this->dpi = dpi; | |||||
} | |||||
/// Returns the index of the display. | |||||
[[nodiscard]] inline int get_index() const noexcept | |||||
{ | |||||
return index; | |||||
} | |||||
/// Returns the name of the display. | |||||
[[nodiscard]] inline const std::string& get_name() const noexcept | |||||
{ | |||||
return name; | |||||
} | |||||
/// Returns the size of the display, in display units. | |||||
[[nodiscard]] inline const math::vector<int, 2>& get_size() const noexcept | |||||
{ | |||||
return size; | |||||
} | |||||
/// Returns the refresh rate of the display, in Hz. | |||||
[[nodiscard]] inline int get_refresh_rate() const noexcept | |||||
{ | |||||
return refresh_rate; | |||||
} | |||||
/// Returns the DPI of the display. | |||||
[[nodiscard]] inline float get_dpi() const noexcept | |||||
{ | |||||
return dpi; | |||||
} | |||||
private: | |||||
int index; | |||||
std::string name; | |||||
math::vector<int, 2> size; | |||||
int refresh_rate; | |||||
float dpi; | |||||
}; | |||||
} // namespace app | |||||
#endif // ANTKEEPER_APP_DISPLAY_HPP |
@ -0,0 +1,130 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "app/input-manager.hpp" | |||||
#include "app/sdl/sdl-input-manager.hpp" | |||||
namespace app { | |||||
input_manager* input_manager::instance() | |||||
{ | |||||
return new sdl_input_manager(); | |||||
} | |||||
void input_manager::register_device(input::device& device) | |||||
{ | |||||
switch (device.get_device_type()) | |||||
{ | |||||
case input::device_type::gamepad: | |||||
register_gamepad(static_cast<input::gamepad&>(device)); | |||||
break; | |||||
case input::device_type::keyboard: | |||||
register_keyboard(static_cast<input::keyboard&>(device)); | |||||
break; | |||||
case input::device_type::mouse: | |||||
register_mouse(static_cast<input::mouse&>(device)); | |||||
break; | |||||
default: | |||||
//std::unreachable(); | |||||
break; | |||||
} | |||||
} | |||||
void input_manager::register_gamepad(input::gamepad& device) | |||||
{ | |||||
// Connect gamepad event signals to the event queue | |||||
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_axis_moved_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_button_pressed_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_button_released_channel().subscribe(event_queue)); | |||||
// Add gamepad to list of gamepads | |||||
gamepads.emplace(&device); | |||||
} | |||||
void input_manager::register_keyboard(input::keyboard& device) | |||||
{ | |||||
// Connect keyboard event signals to the event queue | |||||
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_key_pressed_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_key_released_channel().subscribe(event_queue)); | |||||
// Add keyboard to list of keyboards | |||||
keyboards.emplace(&device); | |||||
} | |||||
void input_manager::register_mouse(input::mouse& device) | |||||
{ | |||||
// Connect mouse event signals to the event queue | |||||
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_button_pressed_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_button_released_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_moved_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_scrolled_channel().subscribe(event_queue)); | |||||
// Add mouse to list of mice | |||||
mice.emplace(&device); | |||||
} | |||||
void input_manager::unregister_device(input::device& device) | |||||
{ | |||||
subscriptions.erase(&device); | |||||
switch (device.get_device_type()) | |||||
{ | |||||
case input::device_type::gamepad: | |||||
unregister_gamepad(static_cast<input::gamepad&>(device)); | |||||
break; | |||||
case input::device_type::keyboard: | |||||
unregister_keyboard(static_cast<input::keyboard&>(device)); | |||||
break; | |||||
case input::device_type::mouse: | |||||
unregister_mouse(static_cast<input::mouse&>(device)); | |||||
break; | |||||
default: | |||||
//std::unreachable(); | |||||
break; | |||||
} | |||||
} | |||||
void input_manager::unregister_gamepad(input::gamepad& gamepad) | |||||
{ | |||||
gamepads.erase(&gamepad); | |||||
} | |||||
void input_manager::unregister_keyboard(input::keyboard& keyboard) | |||||
{ | |||||
keyboards.erase(&keyboard); | |||||
} | |||||
void input_manager::unregister_mouse(input::mouse& mouse) | |||||
{ | |||||
mice.erase(&mouse); | |||||
} | |||||
} // namespace app |
@ -0,0 +1,310 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "app/sdl/sdl-input-manager.hpp" | |||||
#include "debug/log.hpp" | |||||
#include "math/map.hpp" | |||||
#include <SDL2/SDL.h> | |||||
#include <stdexcept> | |||||
namespace app { | |||||
sdl_input_manager::sdl_input_manager() | |||||
{ | |||||
// Init SDL joystick and controller subsystems | |||||
debug::log::trace("Initializing SDL joystick and controller subsystems..."); | |||||
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) | |||||
{ | |||||
debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to initialize SDL joystick and controller subsytems"); | |||||
} | |||||
else | |||||
{ | |||||
debug::log::trace("Initialized SDL joystick and controller subsystems"); | |||||
} | |||||
// Register keyboard and mouse | |||||
register_keyboard(keyboard); | |||||
register_mouse(mouse); | |||||
// Generate keyboard and mouse device connected events | |||||
keyboard.connect(); | |||||
mouse.connect(); | |||||
} | |||||
sdl_input_manager::~sdl_input_manager() | |||||
{ | |||||
// Quit SDL joystick and controller subsystems | |||||
debug::log::trace("Quitting SDL joystick and controller subsystems..."); | |||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); | |||||
debug::log::trace("Quit SDL joystick and controller subsystems..."); | |||||
} | |||||
void sdl_input_manager::update() | |||||
{ | |||||
// Active modifier keys | |||||
std::uint16_t sdl_key_mod = KMOD_NONE; | |||||
std::uint16_t modifier_keys = input::modifier_key::none; | |||||
// Gather SDL events from event queue | |||||
SDL_PumpEvents(); | |||||
// Handle OS events | |||||
for (;;) | |||||
{ | |||||
// Get next display or window event | |||||
SDL_Event event; | |||||
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LOCALECHANGED); | |||||
if (!status) | |||||
{ | |||||
break; | |||||
} | |||||
else if (status < 0) | |||||
{ | |||||
debug::log::error("Failed to peep SDL events: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to peep SDL events"); | |||||
} | |||||
if (event.type == SDL_QUIT) | |||||
{ | |||||
//... | |||||
} | |||||
} | |||||
// Handle keyboard, mouse, and gamepad events | |||||
for (;;) | |||||
{ | |||||
// Get next display or window event | |||||
SDL_Event event; | |||||
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_LASTEVENT); | |||||
if (!status) | |||||
{ | |||||
break; | |||||
} | |||||
else if (status < 0) | |||||
{ | |||||
debug::log::error("Failed to peep SDL events: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to peep SDL events"); | |||||
} | |||||
switch (event.type) | |||||
{ | |||||
[[likely]] case SDL_MOUSEMOTION: | |||||
{ | |||||
mouse.move({event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel}); | |||||
break; | |||||
} | |||||
case SDL_KEYDOWN: | |||||
case SDL_KEYUP: | |||||
{ | |||||
// Get scancode of key | |||||
const input::scancode scancode = static_cast<input::scancode>(event.key.keysym.scancode); | |||||
// Rebuild modifier keys bit mask | |||||
if (event.key.keysym.mod != sdl_key_mod) | |||||
{ | |||||
sdl_key_mod = event.key.keysym.mod; | |||||
modifier_keys = input::modifier_key::none; | |||||
if (sdl_key_mod & KMOD_LSHIFT) | |||||
modifier_keys |= input::modifier_key::left_shift; | |||||
if (sdl_key_mod & KMOD_RSHIFT) | |||||
modifier_keys |= input::modifier_key::right_shift; | |||||
if (sdl_key_mod & KMOD_LCTRL) | |||||
modifier_keys |= input::modifier_key::left_ctrl; | |||||
if (sdl_key_mod & KMOD_RCTRL) | |||||
modifier_keys |= input::modifier_key::right_ctrl; | |||||
if (sdl_key_mod & KMOD_LGUI) | |||||
modifier_keys |= input::modifier_key::left_gui; | |||||
if (sdl_key_mod & KMOD_RGUI) | |||||
modifier_keys |= input::modifier_key::right_gui; | |||||
if (sdl_key_mod & KMOD_NUM) | |||||
modifier_keys |= input::modifier_key::num_lock; | |||||
if (sdl_key_mod & KMOD_CAPS) | |||||
modifier_keys |= input::modifier_key::caps_lock; | |||||
if (sdl_key_mod & KMOD_SCROLL) | |||||
modifier_keys |= input::modifier_key::scroll_lock; | |||||
if (sdl_key_mod & KMOD_MODE) | |||||
modifier_keys |= input::modifier_key::alt_gr; | |||||
} | |||||
// Determine if event was generated from a key repeat | |||||
const bool repeat = event.key.repeat > 0; | |||||
if (event.type == SDL_KEYDOWN) | |||||
{ | |||||
keyboard.press(scancode, repeat, modifier_keys); | |||||
} | |||||
else | |||||
{ | |||||
keyboard.release(scancode, repeat, modifier_keys); | |||||
} | |||||
break; | |||||
} | |||||
case SDL_MOUSEWHEEL: | |||||
{ | |||||
const float flip = (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f; | |||||
mouse.scroll({event.wheel.preciseX * flip, event.wheel.preciseY * flip}); | |||||
break; | |||||
} | |||||
case SDL_MOUSEBUTTONDOWN: | |||||
{ | |||||
mouse.press(static_cast<input::mouse_button>(event.button.button)); | |||||
break; | |||||
} | |||||
case SDL_MOUSEBUTTONUP: | |||||
{ | |||||
mouse.release(static_cast<input::mouse_button>(event.button.button)); | |||||
break; | |||||
} | |||||
[[likely]] case SDL_CONTROLLERAXISMOTION: | |||||
{ | |||||
if (event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID) | |||||
{ | |||||
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
// Map axis position onto `[-1, 1]`. | |||||
const float position = math::map | |||||
( | |||||
static_cast<float>(event.caxis.value), | |||||
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::min()), | |||||
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::max()), | |||||
-1.0f, | |||||
1.0f | |||||
); | |||||
// Generate gamepad axis moved event | |||||
it->second->move(static_cast<input::gamepad_axis>(event.caxis.axis), position); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
case SDL_CONTROLLERBUTTONDOWN: | |||||
{ | |||||
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID) | |||||
{ | |||||
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
it->second->press(static_cast<input::gamepad_button>(event.cbutton.button)); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
case SDL_CONTROLLERBUTTONUP: | |||||
{ | |||||
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID) | |||||
{ | |||||
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
it->second->release(static_cast<input::gamepad_button>(event.cbutton.button)); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
[[unlikely]] case SDL_CONTROLLERDEVICEADDED: | |||||
{ | |||||
if (SDL_IsGameController(event.cdevice.which)) | |||||
{ | |||||
SDL_GameController* sdl_controller = SDL_GameControllerOpen(event.cdevice.which); | |||||
const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which); | |||||
if (sdl_controller) | |||||
{ | |||||
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
// Gamepad reconnected | |||||
debug::log::info("Reconnected gamepad \"{}\"", controller_name); | |||||
it->second->connect(); | |||||
} | |||||
else | |||||
{ | |||||
// Get gamepad GUID | |||||
SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller); | |||||
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick); | |||||
// Copy into UUID struct | |||||
::uuid gamepad_uuid; | |||||
std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size()); | |||||
debug::log::info("Connected gamepad \"{}\" with UUID {}", controller_name, gamepad_uuid.string()); | |||||
// Create new gamepad | |||||
input::gamepad* gamepad = new input::gamepad(); | |||||
gamepad->set_uuid(gamepad_uuid); | |||||
// Add gamepad to gamepad map | |||||
gamepad_map[event.cdevice.which] = gamepad; | |||||
// Register gamepad | |||||
register_device(*gamepad); | |||||
// Generate gamepad connected event | |||||
gamepad->connect(); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
debug::log::error("Failed to connected gamepad \"{}\": {}", controller_name, SDL_GetError()); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
[[unlikely]] case SDL_CONTROLLERDEVICEREMOVED: | |||||
{ | |||||
SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(event.cdevice.which); | |||||
if (sdl_controller) | |||||
{ | |||||
const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which); | |||||
SDL_GameControllerClose(sdl_controller); | |||||
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
it->second->disconnect(); | |||||
} | |||||
debug::log::info("Disconnected gamepad \"{}\"", controller_name); | |||||
} | |||||
break; | |||||
} | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
// Flush event queue | |||||
this->event_queue.flush(); | |||||
} | |||||
} // namespace app |
@ -0,0 +1,55 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP | |||||
#define ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP | |||||
#include "app/input-manager.hpp" | |||||
namespace app { | |||||
class sdl_window; | |||||
/** | |||||
* | |||||
*/ | |||||
class sdl_input_manager: public input_manager | |||||
{ | |||||
public: | |||||
/** | |||||
* Constructs an SDL input manager. | |||||
*/ | |||||
sdl_input_manager(); | |||||
/** | |||||
* Destructs an SDL input manager. | |||||
*/ | |||||
virtual ~sdl_input_manager(); | |||||
virtual void update(); | |||||
private: | |||||
input::keyboard keyboard; | |||||
input::mouse mouse; | |||||
std::unordered_map<int, input::gamepad*> gamepad_map; | |||||
}; | |||||
} // namespace app | |||||
#endif // ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP |
@ -0,0 +1,324 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "app/sdl/sdl-window-manager.hpp" | |||||
#include "app/sdl/sdl-window.hpp" | |||||
#include "debug/log.hpp" | |||||
#include "config.hpp" | |||||
#include <stdexcept> | |||||
namespace app { | |||||
sdl_window_manager::sdl_window_manager() | |||||
{ | |||||
// Init SDL events and video subsystems | |||||
debug::log::trace("Initializing SDL events and video subsystems..."); | |||||
if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0) | |||||
{ | |||||
debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to initialize SDL events and video subsystems"); | |||||
} | |||||
debug::log::trace("Initialized SDL events and video subsystems"); | |||||
// Query displays | |||||
const int display_count = SDL_GetNumVideoDisplays(); | |||||
if (display_count < 1) | |||||
{ | |||||
debug::log::warning("No displays detected: {}", SDL_GetError()); | |||||
} | |||||
else | |||||
{ | |||||
debug::log::info("Display count: {}", display_count); | |||||
displays.resize(display_count); | |||||
for (int i = 0; i < display_count; ++i) | |||||
{ | |||||
// Query display mode | |||||
SDL_DisplayMode display_mode; | |||||
if (SDL_GetDesktopDisplayMode(i, &display_mode) != 0) | |||||
{ | |||||
debug::log::error("Failed to get mode of display {}: {}", i, SDL_GetError()); | |||||
SDL_ClearError(); | |||||
continue; | |||||
} | |||||
// Query display name | |||||
const char* display_name = SDL_GetDisplayName(i); | |||||
if (!display_name) | |||||
{ | |||||
debug::log::warning("Failed to get name of display {}: {}", i, SDL_GetError()); | |||||
SDL_ClearError(); | |||||
display_name = ""; | |||||
} | |||||
// Query display DPI | |||||
float display_dpi; | |||||
if (SDL_GetDisplayDPI(i, &display_dpi, nullptr, nullptr) != 0) | |||||
{ | |||||
const float default_dpi = 96.0f; | |||||
debug::log::warning("Failed to get DPI of display {}: {}; Defaulting to {} DPI", i, SDL_GetError(), default_dpi); | |||||
SDL_ClearError(); | |||||
} | |||||
// Update display properties | |||||
display& display = displays[i]; | |||||
display.set_index(i); | |||||
display.set_name(display_name); | |||||
display.set_size({display_mode.w, display_mode.h}); | |||||
display.set_refresh_rate(display_mode.refresh_rate); | |||||
display.set_dpi(display_dpi); | |||||
// Log display information | |||||
debug::log::info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, display_name, display_mode.w, display_mode.h, display_mode.refresh_rate, display_dpi); | |||||
} | |||||
} | |||||
// Load OpenGL library | |||||
debug::log::trace("Loading OpenGL library..."); | |||||
if (SDL_GL_LoadLibrary(nullptr) != 0) | |||||
{ | |||||
debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to load OpenGL library"); | |||||
} | |||||
debug::log::trace("Loaded OpenGL library"); | |||||
// Set OpenGL-related window creation hints | |||||
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); | |||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); | |||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size); | |||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size); | |||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size); | |||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size); | |||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size); | |||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size); | |||||
} | |||||
sdl_window_manager::~sdl_window_manager() | |||||
{ | |||||
// Quit SDL video subsystem | |||||
debug::log::trace("Quitting SDL video subsystem..."); | |||||
SDL_QuitSubSystem(SDL_INIT_VIDEO); | |||||
debug::log::trace("Quit SDL video subsystem"); | |||||
} | |||||
window* sdl_window_manager::create_window | |||||
( | |||||
const std::string& title, | |||||
const math::vector<int, 2>& windowed_position, | |||||
const math::vector<int, 2>& windowed_size, | |||||
bool maximized, | |||||
bool fullscreen, | |||||
bool v_sync | |||||
) | |||||
{ | |||||
// Create new window | |||||
app::sdl_window* window = new app::sdl_window | |||||
( | |||||
title, | |||||
windowed_position, | |||||
windowed_size, | |||||
maximized, | |||||
fullscreen, | |||||
v_sync | |||||
); | |||||
// Map internal SDL window to window | |||||
window_map[window->internal_window] = window; | |||||
return window; | |||||
} | |||||
void sdl_window_manager::update() | |||||
{ | |||||
// Gather SDL events from event queue | |||||
SDL_PumpEvents(); | |||||
for (;;) | |||||
{ | |||||
// Get next window or display event | |||||
SDL_Event event; | |||||
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_DISPLAYEVENT, SDL_SYSWMEVENT); | |||||
if (!status) | |||||
{ | |||||
break; | |||||
} | |||||
else if (status < 0) | |||||
{ | |||||
debug::log::error("Failed to peep SDL events: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to peep SDL events"); | |||||
} | |||||
// Handle event | |||||
if (event.type == SDL_WINDOWEVENT) | |||||
{ | |||||
switch (event.window.event) | |||||
{ | |||||
case SDL_WINDOWEVENT_SIZE_CHANGED: | |||||
{ | |||||
// Get window | |||||
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); | |||||
app::sdl_window* window = get_window(internal_window); | |||||
// Update window state | |||||
window->size = {event.window.data1, event.window.data2}; | |||||
const auto window_flags = SDL_GetWindowFlags(internal_window); | |||||
if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & SDL_WINDOW_FULLSCREEN)) | |||||
{ | |||||
window->windowed_size = window->size; | |||||
} | |||||
SDL_GL_GetDrawableSize(internal_window, &window->viewport_size.x(), &window->viewport_size.y()); | |||||
window->rasterizer->context_resized(window->viewport_size.x(), window->viewport_size.y()); | |||||
// Publish window resized event | |||||
window->resized_publisher.publish({window, window->size}); | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT_MOVED: | |||||
{ | |||||
// Get window | |||||
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); | |||||
app::sdl_window* window = get_window(internal_window); | |||||
// Update window state | |||||
window->position = {event.window.data1, event.window.data2}; | |||||
const auto window_flags = SDL_GetWindowFlags(internal_window); | |||||
if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & SDL_WINDOW_FULLSCREEN)) | |||||
{ | |||||
window->windowed_position = window->position; | |||||
} | |||||
// Publish window moved event | |||||
window->moved_publisher.publish({window, window->position}); | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT_FOCUS_GAINED: | |||||
{ | |||||
// Get window | |||||
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); | |||||
app::sdl_window* window = get_window(internal_window); | |||||
// Publish window focus gained event | |||||
window->focus_changed_publisher.publish({window, true}); | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT_FOCUS_LOST: | |||||
{ | |||||
// Get window | |||||
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); | |||||
app::sdl_window* window = get_window(internal_window); | |||||
// Publish window focus lost event | |||||
window->focus_changed_publisher.publish({window, false}); | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT_MAXIMIZED: | |||||
{ | |||||
// Get window | |||||
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); | |||||
app::sdl_window* window = get_window(internal_window); | |||||
// Update window state | |||||
window->maximized = true; | |||||
// Publish window maximized event | |||||
window->maximized_publisher.publish({window}); | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT_RESTORED: | |||||
{ | |||||
// Get window | |||||
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); | |||||
app::sdl_window* window = get_window(internal_window); | |||||
// Update window state | |||||
window->maximized = false; | |||||
// Publish window restored event | |||||
window->restored_publisher.publish({window}); | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT_MINIMIZED: | |||||
{ | |||||
// Get window | |||||
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); | |||||
app::sdl_window* window = get_window(internal_window); | |||||
// Publish window minimized event | |||||
window->minimized_publisher.publish({window}); | |||||
break; | |||||
} | |||||
[[unlikely]] case SDL_WINDOWEVENT_CLOSE: | |||||
{ | |||||
// Get window | |||||
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID); | |||||
app::sdl_window* window = get_window(internal_window); | |||||
// Publish window closed event | |||||
window->closed_publisher.publish({window}); | |||||
break; | |||||
} | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
else if (event.type == SDL_DISPLAYEVENT) | |||||
{ | |||||
} | |||||
} | |||||
} | |||||
sdl_window* sdl_window_manager::get_window(SDL_Window* internal_window) | |||||
{ | |||||
sdl_window* window = nullptr; | |||||
if (auto i = window_map.find(internal_window); i != window_map.end()) | |||||
{ | |||||
window = i->second; | |||||
} | |||||
else | |||||
{ | |||||
throw std::runtime_error("SDL window unrecognized by SDL window manager"); | |||||
} | |||||
return window; | |||||
} | |||||
std::size_t sdl_window_manager::get_display_count() const | |||||
{ | |||||
return displays.size(); | |||||
} | |||||
const display& sdl_window_manager::get_display(std::size_t index) const | |||||
{ | |||||
return displays[index]; | |||||
} | |||||
} // namespace app |
@ -0,0 +1,74 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP | |||||
#define ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP | |||||
#include "app/window-manager.hpp" | |||||
#include "app/display.hpp" | |||||
#include <SDL2/SDL.h> | |||||
#include <unordered_map> | |||||
#include <vector> | |||||
namespace app { | |||||
class sdl_window; | |||||
/** | |||||
* | |||||
*/ | |||||
class sdl_window_manager: public window_manager | |||||
{ | |||||
public: | |||||
/** | |||||
* Constructs an SDL window manager. | |||||
*/ | |||||
sdl_window_manager(); | |||||
/** | |||||
* Destructs an SDL window manager. | |||||
*/ | |||||
virtual ~sdl_window_manager(); | |||||
virtual void update(); | |||||
/// @copydoc window::window() | |||||
[[nodiscard]] virtual window* create_window | |||||
( | |||||
const std::string& title, | |||||
const math::vector<int, 2>& windowed_position, | |||||
const math::vector<int, 2>& windowed_size, | |||||
bool maximized, | |||||
bool fullscreen, | |||||
bool v_sync | |||||
); | |||||
[[nodiscard]] virtual std::size_t get_display_count() const; | |||||
[[nodiscard]] virtual const display& get_display(std::size_t index) const; | |||||
private: | |||||
sdl_window* get_window(SDL_Window* internal_window); | |||||
std::vector<display> displays; | |||||
std::unordered_map<SDL_Window*, app::sdl_window*> window_map; | |||||
}; | |||||
} // namespace app | |||||
#endif // ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP |
@ -0,0 +1,294 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "app/sdl/sdl-window.hpp" | |||||
#include "config.hpp" | |||||
#include "debug/log.hpp" | |||||
#include <glad/glad.h> | |||||
#include <stdexcept> | |||||
namespace app { | |||||
sdl_window::sdl_window | |||||
( | |||||
const std::string& title, | |||||
const math::vector<int, 2>& windowed_position, | |||||
const math::vector<int, 2>& windowed_size, | |||||
bool maximized, | |||||
bool fullscreen, | |||||
bool v_sync | |||||
) | |||||
{ | |||||
// Determine SDL window creation flags | |||||
Uint32 window_flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; | |||||
if (maximized) | |||||
{ | |||||
window_flags |= SDL_WINDOW_MAXIMIZED; | |||||
} | |||||
if (fullscreen) | |||||
{ | |||||
window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |||||
} | |||||
// Create SDL window | |||||
debug::log::trace("Creating SDL window..."); | |||||
internal_window = SDL_CreateWindow | |||||
( | |||||
title.c_str(), | |||||
windowed_position.x(), | |||||
windowed_position.y(), | |||||
windowed_size.x(), | |||||
windowed_size.y(), | |||||
window_flags | |||||
); | |||||
if (!internal_window) | |||||
{ | |||||
debug::log::fatal("Failed to create SDL window: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to create SDL window"); | |||||
} | |||||
debug::log::trace("Created SDL window"); | |||||
// Create OpenGL context | |||||
debug::log::trace("Creating OpenGL context..."); | |||||
internal_context = SDL_GL_CreateContext(internal_window); | |||||
if (!internal_context) | |||||
{ | |||||
debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to create OpenGL context"); | |||||
} | |||||
debug::log::trace("Created OpenGL context"); | |||||
// Query OpenGL context info | |||||
int opengl_context_version_major = -1; | |||||
int opengl_context_version_minor = -1; | |||||
int opengl_context_red_size = -1; | |||||
int opengl_context_green_size = -1; | |||||
int opengl_context_blue_size = -1; | |||||
int opengl_context_alpha_size = -1; | |||||
int opengl_context_depth_size = -1; | |||||
int opengl_context_stencil_size = -1; | |||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &opengl_context_version_major); | |||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &opengl_context_version_minor); | |||||
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &opengl_context_red_size); | |||||
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &opengl_context_green_size); | |||||
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &opengl_context_blue_size); | |||||
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size); | |||||
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size); | |||||
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size); | |||||
// Log OpenGL context info | |||||
debug::log::info | |||||
( | |||||
"OpenGL context version: {}.{}; format: R{}G{}B{}A{}D{}S{}", | |||||
opengl_context_version_major, | |||||
opengl_context_version_minor, | |||||
opengl_context_red_size, | |||||
opengl_context_green_size, | |||||
opengl_context_blue_size, | |||||
opengl_context_alpha_size, | |||||
opengl_context_depth_size, | |||||
opengl_context_stencil_size | |||||
); | |||||
// Compare OpenGL context version with requested version | |||||
if (opengl_context_version_major != config::opengl_version_major || | |||||
opengl_context_version_minor != config::opengl_version_minor) | |||||
{ | |||||
debug::log::warning("Requested OpenGL context version {}.{} but got version {}.{}", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor); | |||||
} | |||||
// Compare OpenGL context format with requested format | |||||
if (opengl_context_red_size < config::opengl_min_red_size || | |||||
opengl_context_green_size < config::opengl_min_green_size || | |||||
opengl_context_blue_size < config::opengl_min_blue_size || | |||||
opengl_context_alpha_size < config::opengl_min_alpha_size || | |||||
opengl_context_depth_size < config::opengl_min_depth_size || | |||||
opengl_context_stencil_size < config::opengl_min_stencil_size) | |||||
{ | |||||
debug::log::warning | |||||
( | |||||
"OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})", | |||||
opengl_context_red_size, | |||||
opengl_context_green_size, | |||||
opengl_context_blue_size, | |||||
opengl_context_alpha_size, | |||||
opengl_context_depth_size, | |||||
opengl_context_stencil_size, | |||||
config::opengl_min_red_size, | |||||
config::opengl_min_green_size, | |||||
config::opengl_min_blue_size, | |||||
config::opengl_min_alpha_size, | |||||
config::opengl_min_depth_size, | |||||
config::opengl_min_stencil_size | |||||
); | |||||
} | |||||
// Load OpenGL functions via GLAD | |||||
debug::log::trace("Loading OpenGL functions..."); | |||||
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) | |||||
{ | |||||
debug::log::fatal("Failed to load OpenGL functions", SDL_GetError()); | |||||
throw std::runtime_error("Failed to load OpenGL functions"); | |||||
} | |||||
debug::log::trace("Loaded OpenGL functions"); | |||||
// Log OpenGL information | |||||
debug::log::info | |||||
( | |||||
"OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}", | |||||
reinterpret_cast<const char*>(glGetString(GL_VENDOR)), | |||||
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), | |||||
reinterpret_cast<const char*>(glGetString(GL_VERSION)), | |||||
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION)) | |||||
); | |||||
// Fill window with color | |||||
//glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |||||
glClear(GL_COLOR_BUFFER_BIT); | |||||
swap_buffers(); | |||||
// Enable or disable v-sync | |||||
set_v_sync(v_sync); | |||||
// Update window state | |||||
this->title = title; | |||||
this->windowed_position = windowed_position; | |||||
this->windowed_size = windowed_size; | |||||
this->maximized = maximized; | |||||
this->fullscreen = fullscreen; | |||||
SDL_GetWindowPosition(internal_window, &this->position.x(), &this->position.y()); | |||||
SDL_GetWindowSize(internal_window, &this->size.x(), &this->size.y()); | |||||
SDL_GetWindowMinimumSize(internal_window, &this->minimum_size.x(), &this->minimum_size.y()); | |||||
SDL_GetWindowMaximumSize(internal_window, &this->maximum_size.x(), &this->maximum_size.y()); | |||||
SDL_GL_GetDrawableSize(internal_window, &this->viewport_size.x(), &this->viewport_size.y()); | |||||
// Allocate rasterizer | |||||
this->rasterizer = new gl::rasterizer(); | |||||
} | |||||
sdl_window::~sdl_window() | |||||
{ | |||||
// Destruct rasterizer | |||||
delete rasterizer; | |||||
// Destruct the OpenGL context | |||||
SDL_GL_DeleteContext(internal_context); | |||||
// Destruct the SDL window | |||||
SDL_DestroyWindow(internal_window); | |||||
} | |||||
void sdl_window::set_title(const std::string& title) | |||||
{ | |||||
SDL_SetWindowTitle(internal_window, title.c_str()); | |||||
this->title = title; | |||||
} | |||||
void sdl_window::set_position(const math::vector<int, 2>& position) | |||||
{ | |||||
SDL_SetWindowPosition(internal_window, position.x(), position.y()); | |||||
} | |||||
void sdl_window::set_size(const math::vector<int, 2>& size) | |||||
{ | |||||
SDL_SetWindowSize(internal_window, size.x(), size.y()); | |||||
} | |||||
void sdl_window::set_minimum_size(const math::vector<int, 2>& size) | |||||
{ | |||||
SDL_SetWindowMinimumSize(internal_window, size.x(), size.y()); | |||||
this->minimum_size = size; | |||||
} | |||||
void sdl_window::set_maximum_size(const math::vector<int, 2>& size) | |||||
{ | |||||
SDL_SetWindowMaximumSize(internal_window, size.x(), size.y()); | |||||
this->maximum_size = size; | |||||
} | |||||
void sdl_window::set_maximized(bool maximized) | |||||
{ | |||||
if (maximized) | |||||
{ | |||||
SDL_MaximizeWindow(internal_window); | |||||
} | |||||
else | |||||
{ | |||||
SDL_RestoreWindow(internal_window); | |||||
} | |||||
} | |||||
void sdl_window::set_fullscreen(bool fullscreen) | |||||
{ | |||||
SDL_SetWindowFullscreen(internal_window, (fullscreen) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); | |||||
this->fullscreen = fullscreen; | |||||
} | |||||
void sdl_window::set_v_sync(bool v_sync) | |||||
{ | |||||
if (v_sync) | |||||
{ | |||||
debug::log::trace("Enabling adaptive v-sync..."); | |||||
if (SDL_GL_SetSwapInterval(-1) != 0) | |||||
{ | |||||
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError()); | |||||
debug::log::trace("Enabling synchronized v-sync..."); | |||||
if (SDL_GL_SetSwapInterval(1) != 0) | |||||
{ | |||||
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError()); | |||||
v_sync = false; | |||||
} | |||||
else | |||||
{ | |||||
debug::log::debug("Enabled synchronized v-sync"); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
debug::log::debug("Enabled adaptive v-sync"); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
debug::log::trace("Disabling v-sync..."); | |||||
if (SDL_GL_SetSwapInterval(0) != 0) | |||||
{ | |||||
debug::log::error("Failed to disable v-sync: {}", SDL_GetError()); | |||||
v_sync = true; | |||||
} | |||||
else | |||||
{ | |||||
debug::log::debug("Disabled v-sync"); | |||||
} | |||||
} | |||||
this->v_sync = v_sync; | |||||
} | |||||
void sdl_window::make_current() | |||||
{ | |||||
SDL_GL_MakeCurrent(internal_window, internal_context); | |||||
} | |||||
void sdl_window::swap_buffers() | |||||
{ | |||||
SDL_GL_SwapWindow(internal_window); | |||||
} | |||||
} // namespace app |
@ -0,0 +1,76 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_APP_SDL_WINDOW_HPP | |||||
#define ANTKEEPER_APP_SDL_WINDOW_HPP | |||||
#include "app/window.hpp" | |||||
#include <SDL2/SDL.h> | |||||
namespace app { | |||||
/** | |||||
* | |||||
*/ | |||||
class sdl_window: public window | |||||
{ | |||||
public: | |||||
virtual ~sdl_window(); | |||||
virtual void set_title(const std::string& title); | |||||
virtual void set_position(const math::vector<int, 2>& position); | |||||
virtual void set_size(const math::vector<int, 2>& size); | |||||
virtual void set_minimum_size(const math::vector<int, 2>& size); | |||||
virtual void set_maximum_size(const math::vector<int, 2>& size); | |||||
virtual void set_maximized(bool maximized); | |||||
virtual void set_fullscreen(bool fullscreen); | |||||
virtual void set_v_sync(bool v_sync); | |||||
virtual void make_current(); | |||||
virtual void swap_buffers(); | |||||
[[nodiscard]] inline virtual gl::rasterizer* get_rasterizer() noexcept | |||||
{ | |||||
return rasterizer; | |||||
} | |||||
private: | |||||
friend class sdl_window_manager; | |||||
sdl_window | |||||
( | |||||
const std::string& title, | |||||
const math::vector<int, 2>& windowed_position, | |||||
const math::vector<int, 2>& windowed_size, | |||||
bool maximized, | |||||
bool fullscreen, | |||||
bool v_sync | |||||
); | |||||
sdl_window(const sdl_window&) = delete; | |||||
sdl_window(sdl_window&&) = delete; | |||||
sdl_window& operator=(const sdl_window&) = delete; | |||||
sdl_window& operator=(sdl_window&&) = delete; | |||||
SDL_Window* internal_window; | |||||
SDL_GLContext internal_context; | |||||
gl::rasterizer* rasterizer; | |||||
}; | |||||
} // namespace app | |||||
#endif // ANTKEEPER_APP_SDL_WINDOW_HPP |
@ -0,0 +1,103 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_APP_WINDOW_EVENTS_HPP | |||||
#define ANTKEEPER_APP_WINDOW_EVENTS_HPP | |||||
#include "math/vector.hpp" | |||||
namespace app { | |||||
class window; | |||||
/** | |||||
* Event generated when a window has been requested to close. | |||||
*/ | |||||
struct window_closed_event | |||||
{ | |||||
/// Pointer to the window that has been requested to close. | |||||
window* window; | |||||
}; | |||||
/** | |||||
* Event generated when a window has gained or lost focus. | |||||
*/ | |||||
struct window_focus_changed_event | |||||
{ | |||||
/// Pointer to the window that has gained or lost focus. | |||||
window* window; | |||||
/// `true` if the window is in focus, `false` otherwise. | |||||
bool in_focus; | |||||
}; | |||||
/** | |||||
* Event generated when a window has been moved. | |||||
*/ | |||||
struct window_moved_event | |||||
{ | |||||
/// Pointer to the window that has been moved. | |||||
window* window; | |||||
/// Position of the window, in display units. | |||||
math::vector<int, 2> position; | |||||
}; | |||||
/** | |||||
* Event generated when a window has been maximized. | |||||
*/ | |||||
struct window_maximized_event | |||||
{ | |||||
/// Pointer to the window that has been maximized. | |||||
window* window; | |||||
}; | |||||
/** | |||||
* Event generated when a window has been minimized. | |||||
*/ | |||||
struct window_minimized_event | |||||
{ | |||||
/// Pointer to the window that has been minimized. | |||||
window* window; | |||||
}; | |||||
/** | |||||
* Event generated when a window has been restored. | |||||
*/ | |||||
struct window_restored_event | |||||
{ | |||||
/// Pointer to the window that has been restored. | |||||
window* window; | |||||
}; | |||||
/** | |||||
* Event generated when a window has been resized. | |||||
*/ | |||||
struct window_resized_event | |||||
{ | |||||
/// Pointer to the window that has been resized. | |||||
window* window; | |||||
/// Window size, in display units. | |||||
math::vector<int, 2> size; | |||||
}; | |||||
} // namespace app | |||||
#endif // ANTKEEPER_APP_WINDOW_EVENTS_HPP |
@ -0,0 +1,30 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "app/window-manager.hpp" | |||||
#include "app/sdl/sdl-window-manager.hpp" | |||||
namespace app { | |||||
window_manager* window_manager::instance() | |||||
{ | |||||
return new sdl_window_manager(); | |||||
} | |||||
} // namespace app |
@ -0,0 +1,84 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_APP_WINDOW_MANAGER_HPP | |||||
#define ANTKEEPER_APP_WINDOW_MANAGER_HPP | |||||
#include "app/display.hpp" | |||||
#include "app/window.hpp" | |||||
#include "math/vector.hpp" | |||||
#include <string> | |||||
namespace app { | |||||
/** | |||||
* | |||||
*/ | |||||
class window_manager | |||||
{ | |||||
public: | |||||
/** | |||||
* Allocates and returns a window manager. | |||||
*/ | |||||
static window_manager* instance(); | |||||
/// Destructs a window manager. | |||||
virtual ~window_manager() = default; | |||||
/** | |||||
* Updates all managed windows. This should be called once per frame. | |||||
*/ | |||||
virtual void update() = 0; | |||||
/** | |||||
* Constructs a window. | |||||
* | |||||
* @param title Title of the window. | |||||
* @param windowed_position Windowed (non-maximized, non-fullscreen) position of the window, in display units. | |||||
* @param windowed_size Windowed (non-maximized, non-fullscreen) size of the window, in display units. | |||||
* @param maximized `true` if the window should start maximized, `false` otherwise. | |||||
* @param fullscreen `true` if the window should start fullscreen, `false` otherwise. | |||||
* @param v_sync `true` if v-sync should be enabled, `false` otherwise. | |||||
*/ | |||||
[[nodiscard]] virtual window* create_window | |||||
( | |||||
const std::string& title, | |||||
const math::vector<int, 2>& windowed_position, | |||||
const math::vector<int, 2>& windowed_size, | |||||
bool maximized, | |||||
bool fullscreen, | |||||
bool v_sync | |||||
) = 0; | |||||
/// Returns the number of available displays. | |||||
[[nodiscard]] virtual std::size_t get_display_count() const = 0; | |||||
/** | |||||
* Returns the display with the given index. | |||||
* | |||||
* @param index Index of a display. | |||||
* | |||||
* @return Display with the given index. | |||||
*/ | |||||
[[nodiscard]] virtual const display& get_display(std::size_t index) const = 0; | |||||
}; | |||||
} // namespace app | |||||
#endif // ANTKEEPER_APP_WINDOW_MANAGER_HPP |
@ -0,0 +1,26 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "app/window.hpp" | |||||
namespace app { | |||||
} // namespace app |
@ -0,0 +1,252 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_APP_WINDOW_HPP | |||||
#define ANTKEEPER_APP_WINDOW_HPP | |||||
#include "math/vector.hpp" | |||||
#include "event/publisher.hpp" | |||||
#include "app/window-events.hpp" | |||||
#include "gl/rasterizer.hpp" | |||||
#include <string> | |||||
namespace app { | |||||
class window_manager; | |||||
/** | |||||
* | |||||
*/ | |||||
class window | |||||
{ | |||||
public: | |||||
/** | |||||
* Constructs a window. | |||||
*/ | |||||
window() = default; | |||||
/** | |||||
* Destructs a window. | |||||
*/ | |||||
virtual ~window() = default; | |||||
/** | |||||
* Changes the title of the window. | |||||
* | |||||
* @param title Window title. | |||||
*/ | |||||
virtual void set_title(const std::string& title) = 0; | |||||
/** | |||||
* Changes the position of the window. | |||||
* | |||||
* @param position Position of the window, in display units. | |||||
*/ | |||||
virtual void set_position(const math::vector<int, 2>& position) = 0; | |||||
/** | |||||
* Changes the size of the window. | |||||
* | |||||
* @param size Size of the window, in display units. | |||||
*/ | |||||
virtual void set_size(const math::vector<int, 2>& size) = 0; | |||||
/** | |||||
* Sets the minimum size of the window. | |||||
* | |||||
* @param size Minimum size of the window, in display units. | |||||
*/ | |||||
virtual void set_minimum_size(const math::vector<int, 2>& size) = 0; | |||||
/** | |||||
* Sets the maximum size of the window. | |||||
* | |||||
* @param size Maximum size of the window, in display units. | |||||
*/ | |||||
virtual void set_maximum_size(const math::vector<int, 2>& size) = 0; | |||||
/** | |||||
* Maximizes or unmaximizes the window. | |||||
* | |||||
* @param maximized `true` if the window should be maximized, `false` otherwise. | |||||
*/ | |||||
virtual void set_maximized(bool maximized) = 0; | |||||
/** | |||||
* Enables or disables fullscreen mode. | |||||
* | |||||
* @param fullscreen `true` if the window should be in fullscreen mode, `false` otherwise. | |||||
*/ | |||||
virtual void set_fullscreen(bool fullscreen) = 0; | |||||
/** | |||||
* Enables or disables v-sync. | |||||
* | |||||
* @param v_sync `true` if the v-sync should be enabled, `false` otherwise. | |||||
*/ | |||||
virtual void set_v_sync(bool v_sync) = 0; | |||||
/** | |||||
* Makes the window's graphics context current. | |||||
*/ | |||||
virtual void make_current() = 0; | |||||
/** | |||||
* Swaps the front and back buffers of the window's graphics context. | |||||
*/ | |||||
virtual void swap_buffers() = 0; | |||||
/// Returns the title of the window. | |||||
[[nodiscard]] inline const std::string& get_title() const noexcept | |||||
{ | |||||
return title; | |||||
} | |||||
/// Returns the windowed (non-maximized, non-fullscreen) position of the window, in display units. | |||||
[[nodiscard]] inline const math::vector<int, 2>& get_windowed_position() const noexcept | |||||
{ | |||||
return windowed_position; | |||||
} | |||||
/// Returns the current position of the window, in display units. | |||||
[[nodiscard]] inline const math::vector<int, 2>& get_position() const noexcept | |||||
{ | |||||
return position; | |||||
} | |||||
/// Returns the windowed (non-maximized, non-fullscreen) size of the window, in display units. | |||||
[[nodiscard]] inline const math::vector<int, 2>& get_windowed_size() const noexcept | |||||
{ | |||||
return windowed_size; | |||||
} | |||||
/// Returns the current size of the window, in display units. | |||||
[[nodiscard]] inline const math::vector<int, 2>& get_size() const noexcept | |||||
{ | |||||
return size; | |||||
} | |||||
/// Returns the minimum size of the window, in display units. | |||||
[[nodiscard]] inline const math::vector<int, 2>& get_minimum_size() const noexcept | |||||
{ | |||||
return minimum_size; | |||||
} | |||||
/// Returns the maximum size of the window, in display units. | |||||
[[nodiscard]] inline const math::vector<int, 2>& get_maximum_size() const noexcept | |||||
{ | |||||
return minimum_size; | |||||
} | |||||
/// Returns the current size of the window's drawable viewport, in pixels. | |||||
[[nodiscard]] inline const math::vector<int, 2>& get_viewport_size() const noexcept | |||||
{ | |||||
return viewport_size; | |||||
} | |||||
/// Returns `true` if the window is maximized, `false` otherwise. | |||||
[[nodiscard]] inline bool is_maximized() const noexcept | |||||
{ | |||||
return maximized; | |||||
} | |||||
/// Returns `true` if the window is in fullscreen mode, `false` otherwise. | |||||
[[nodiscard]] inline bool is_fullscreen() const noexcept | |||||
{ | |||||
return fullscreen; | |||||
} | |||||
/// Returns `true` if the v-sync is enabled, `false` otherwise. | |||||
[[nodiscard]] inline bool get_v_sync() const noexcept | |||||
{ | |||||
return v_sync; | |||||
} | |||||
/// Returns the rasterizer associated with this window. | |||||
[[nodiscard]] virtual gl::rasterizer* get_rasterizer() noexcept = 0; | |||||
/// Returns the channel through which window closed events are published. | |||||
[[nodiscard]] inline event::channel<window_closed_event>& get_closed_channel() noexcept | |||||
{ | |||||
return closed_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window focus changed events are published. | |||||
[[nodiscard]] inline event::channel<window_focus_changed_event>& get_focus_changed_channel() noexcept | |||||
{ | |||||
return focus_changed_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window maximized events are published. | |||||
[[nodiscard]] inline event::channel<window_maximized_event>& get_maximized_channel() noexcept | |||||
{ | |||||
return maximized_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window minimized events are published. | |||||
[[nodiscard]] inline event::channel<window_minimized_event>& get_minimized_channel() noexcept | |||||
{ | |||||
return minimized_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window moved events are published. | |||||
[[nodiscard]] inline event::channel<window_moved_event>& get_moved_channel() noexcept | |||||
{ | |||||
return moved_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window resized events are published. | |||||
[[nodiscard]] inline event::channel<window_resized_event>& get_resized_channel() noexcept | |||||
{ | |||||
return resized_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window restored events are published. | |||||
[[nodiscard]] inline event::channel<window_restored_event>& get_restored_channel() noexcept | |||||
{ | |||||
return restored_publisher.channel(); | |||||
} | |||||
protected: | |||||
friend class window_manager; | |||||
std::string title; | |||||
math::vector<int, 2> windowed_position; | |||||
math::vector<int, 2> position; | |||||
math::vector<int, 2> windowed_size; | |||||
math::vector<int, 2> size; | |||||
math::vector<int, 2> minimum_size; | |||||
math::vector<int, 2> maximum_size; | |||||
math::vector<int, 2> viewport_size; | |||||
bool maximized; | |||||
bool fullscreen; | |||||
bool v_sync; | |||||
event::publisher<window_closed_event> closed_publisher; | |||||
event::publisher<window_focus_changed_event> focus_changed_publisher; | |||||
event::publisher<window_maximized_event> maximized_publisher; | |||||
event::publisher<window_minimized_event> minimized_publisher; | |||||
event::publisher<window_moved_event> moved_publisher; | |||||
event::publisher<window_resized_event> resized_publisher; | |||||
event::publisher<window_restored_event> restored_publisher; | |||||
}; | |||||
} // namespace app | |||||
#endif // ANTKEEPER_APP_WINDOW_HPP |
@ -1,818 +0,0 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "application.hpp" | |||||
#include "config.hpp" | |||||
#include "debug/log.hpp" | |||||
#include "input/scancode.hpp" | |||||
#include "math/map.hpp" | |||||
#include <SDL2/SDL.h> | |||||
#include <glad/glad.h> | |||||
#include <stdexcept> | |||||
#include <utility> | |||||
application::application | |||||
( | |||||
const std::string& window_title, | |||||
int window_x, | |||||
int window_y, | |||||
int window_w, | |||||
int window_h, | |||||
bool maximized, | |||||
bool fullscreen, | |||||
bool v_sync | |||||
): | |||||
closed(false), | |||||
maximized(false), | |||||
fullscreen(true), | |||||
v_sync(false), | |||||
cursor_visible(true), | |||||
display_size({0, 0}), | |||||
display_dpi(0.0f), | |||||
windowed_position({-1, -1}), | |||||
windowed_size({-1, -1}), | |||||
viewport_size({-1, -1}), | |||||
mouse_position({0, 0}), | |||||
sdl_window(nullptr), | |||||
sdl_gl_context(nullptr) | |||||
{ | |||||
// Log SDL info | |||||
// SDL_version sdl_compiled_version; | |||||
// SDL_version sdl_linked_version; | |||||
// SDL_VERSION(&sdl_compiled_version); | |||||
// SDL_GetVersion(&sdl_linked_version); | |||||
// debug::log::info | |||||
// ( | |||||
// "SDL compiled version: {}.{}.{}; linked version: {}.{}.{}", | |||||
// sdl_compiled_version.major, | |||||
// sdl_compiled_version.minor, | |||||
// sdl_compiled_version.patch, | |||||
// sdl_linked_version.major, | |||||
// sdl_linked_version.minor, | |||||
// sdl_linked_version.patch | |||||
// ); | |||||
// Init SDL events and video subsystems | |||||
debug::log::trace("Initializing SDL events and video subsystems..."); | |||||
if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0) | |||||
{ | |||||
debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to initialize SDL events and video subsystems"); | |||||
} | |||||
debug::log::trace("Initialized SDL events and video subsystems"); | |||||
// Query displays | |||||
debug::log::trace("Querying displays..."); | |||||
const int sdl_display_count = SDL_GetNumVideoDisplays(); | |||||
if (sdl_display_count < 1) | |||||
{ | |||||
debug::log::fatal("No displays detected: {}", SDL_GetError()); | |||||
throw std::runtime_error("No displays detected"); | |||||
} | |||||
debug::log::info("Display count: {}", sdl_display_count); | |||||
for (int i = 0; i < sdl_display_count; ++i) | |||||
{ | |||||
// Query display mode | |||||
SDL_DisplayMode sdl_display_mode; | |||||
if (SDL_GetDesktopDisplayMode(i, &sdl_display_mode) != 0) | |||||
{ | |||||
debug::log::error("Failed to get mode of display {}: {}", i, SDL_GetError()); | |||||
SDL_ClearError(); | |||||
continue; | |||||
} | |||||
// Query display name | |||||
const char* sdl_display_name = SDL_GetDisplayName(i); | |||||
if (!sdl_display_name) | |||||
{ | |||||
debug::log::warning("Failed to get name of display {}: {}", i, SDL_GetError()); | |||||
SDL_ClearError(); | |||||
sdl_display_name = ""; | |||||
} | |||||
// Query display DPI | |||||
float sdl_display_dpi; | |||||
if (SDL_GetDisplayDPI(i, &sdl_display_dpi, nullptr, nullptr) != 0) | |||||
{ | |||||
const float default_dpi = 96.0f; | |||||
debug::log::warning("Failed to get DPI of display {}: {}; Defaulting to {} DPI", i, SDL_GetError(), default_dpi); | |||||
SDL_ClearError(); | |||||
} | |||||
// Log display information | |||||
debug::log::info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, sdl_display_name, sdl_display_mode.w, sdl_display_mode.h, sdl_display_mode.refresh_rate, sdl_display_dpi); | |||||
} | |||||
debug::log::trace("Queried displays"); | |||||
// Detect display dimensions | |||||
SDL_DisplayMode sdl_desktop_display_mode; | |||||
if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0) | |||||
{ | |||||
debug::log::fatal("Failed to detect desktop display mode: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to detect desktop display mode"); | |||||
} | |||||
display_size = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h}; | |||||
// Detect display DPI | |||||
if (SDL_GetDisplayDPI(0, &display_dpi, nullptr, nullptr) != 0) | |||||
{ | |||||
debug::log::fatal("Failed to detect display DPI: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to detect display DPI"); | |||||
} | |||||
// Log display properties | |||||
debug::log::info("Detected {}x{}@{}Hz display with {} DPI", sdl_desktop_display_mode.w, sdl_desktop_display_mode.h, sdl_desktop_display_mode.refresh_rate, display_dpi); | |||||
// Load OpenGL library | |||||
debug::log::trace("Loading OpenGL library..."); | |||||
if (SDL_GL_LoadLibrary(nullptr) != 0) | |||||
{ | |||||
debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to load OpenGL library"); | |||||
} | |||||
debug::log::trace("Loaded OpenGL library"); | |||||
// Set OpenGL-related window creation hints | |||||
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); | |||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size); | |||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size); | |||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size); | |||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size); | |||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size); | |||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size); | |||||
Uint32 sdl_window_flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; | |||||
if (fullscreen) | |||||
{ | |||||
sdl_window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |||||
} | |||||
if (maximized) | |||||
{ | |||||
sdl_window_flags |= SDL_WINDOW_MAXIMIZED; | |||||
} | |||||
if (window_x == -1 && window_y == -1) | |||||
{ | |||||
window_x = SDL_WINDOWPOS_CENTERED; | |||||
window_y = SDL_WINDOWPOS_CENTERED; | |||||
} | |||||
if (window_w <= 0 || window_h <= 0) | |||||
{ | |||||
window_w = sdl_desktop_display_mode.w / 2; | |||||
window_h = sdl_desktop_display_mode.h / 2; | |||||
} | |||||
// Create a hidden fullscreen window | |||||
debug::log::trace("Creating window..."); | |||||
sdl_window = SDL_CreateWindow | |||||
( | |||||
window_title.c_str(), | |||||
window_x, | |||||
window_y, | |||||
window_w, | |||||
window_h, | |||||
sdl_window_flags | |||||
); | |||||
if (!sdl_window) | |||||
{ | |||||
debug::log::fatal("Failed to create {}x{} window: {}", display_size[0], display_size[1], SDL_GetError()); | |||||
throw std::runtime_error("Failed to create SDL window"); | |||||
} | |||||
debug::log::trace("Created window"); | |||||
if (window_x != SDL_WINDOWPOS_CENTERED && window_y != SDL_WINDOWPOS_CENTERED) | |||||
{ | |||||
this->windowed_position = {window_x, window_y}; | |||||
} | |||||
this->windowed_size = {window_w, window_h}; | |||||
this->maximized = maximized; | |||||
this->fullscreen = fullscreen; | |||||
// Set hard window minimum size | |||||
SDL_SetWindowMinimumSize(sdl_window, 160, 120); | |||||
// Create OpenGL context | |||||
debug::log::trace("Creating OpenGL {}.{} context...", config::opengl_version_major, config::opengl_version_minor); | |||||
sdl_gl_context = SDL_GL_CreateContext(sdl_window); | |||||
if (!sdl_gl_context) | |||||
{ | |||||
debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError()); | |||||
throw std::runtime_error("Failed to create OpenGL context"); | |||||
} | |||||
// Log version of created OpenGL context | |||||
int opengl_context_version_major = -1; | |||||
int opengl_context_version_minor = -1; | |||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &opengl_context_version_major); | |||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &opengl_context_version_minor); | |||||
debug::log::info("Created OpenGL {}.{} context", opengl_context_version_major, opengl_context_version_minor); | |||||
// Compare OpenGL context version with requested version | |||||
if (opengl_context_version_major != config::opengl_version_major || | |||||
opengl_context_version_minor != config::opengl_version_minor) | |||||
{ | |||||
debug::log::warning("Requested OpenGL {}.{} context but created OpenGL {}.{} context", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor); | |||||
} | |||||
// Log format of OpenGL context default framebuffer | |||||
int opengl_context_red_size = -1; | |||||
int opengl_context_green_size = -1; | |||||
int opengl_context_blue_size = -1; | |||||
int opengl_context_alpha_size = -1; | |||||
int opengl_context_depth_size = -1; | |||||
int opengl_context_stencil_size = -1; | |||||
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &opengl_context_red_size); | |||||
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &opengl_context_green_size); | |||||
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &opengl_context_blue_size); | |||||
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size); | |||||
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size); | |||||
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size); | |||||
debug::log::info("OpenGL context format: R{}G{}B{}A{}D{}S{}", opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size); | |||||
// Compare OpenGL context default framebuffer format with request format | |||||
if (opengl_context_red_size < config::opengl_min_red_size || | |||||
opengl_context_green_size < config::opengl_min_green_size || | |||||
opengl_context_blue_size < config::opengl_min_blue_size || | |||||
opengl_context_alpha_size < config::opengl_min_alpha_size || | |||||
opengl_context_depth_size < config::opengl_min_depth_size || | |||||
opengl_context_stencil_size < config::opengl_min_stencil_size) | |||||
{ | |||||
debug::log::warning | |||||
( | |||||
"OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})", | |||||
opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size, | |||||
config::opengl_min_red_size, config::opengl_min_green_size, config::opengl_min_blue_size, config::opengl_min_alpha_size, config::opengl_min_depth_size, config::opengl_min_stencil_size | |||||
); | |||||
} | |||||
// Load OpenGL functions via GLAD | |||||
debug::log::trace("Loading OpenGL functions..."); | |||||
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) | |||||
{ | |||||
debug::log::fatal("Failed to load OpenGL functions", SDL_GetError()); | |||||
throw std::runtime_error("Failed to load OpenGL functions"); | |||||
} | |||||
debug::log::trace("Loaded OpenGL functions"); | |||||
// Log OpenGL context information | |||||
debug::log::info | |||||
( | |||||
"OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}", | |||||
reinterpret_cast<const char*>(glGetString(GL_VENDOR)), | |||||
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), | |||||
reinterpret_cast<const char*>(glGetString(GL_VERSION)), | |||||
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION)) | |||||
); | |||||
// Clear window color | |||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |||||
glClear(GL_COLOR_BUFFER_BIT); | |||||
swap_buffers(); | |||||
// Set v-sync mode | |||||
set_v_sync(v_sync); | |||||
// Update viewport size | |||||
SDL_GL_GetDrawableSize(sdl_window, &viewport_size[0], &viewport_size[1]); | |||||
// Init SDL joystick and controller subsystems | |||||
debug::log::trace("Initializing SDL joystick and controller subsystems..."); | |||||
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) | |||||
{ | |||||
debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError()); | |||||
} | |||||
else | |||||
{ | |||||
debug::log::trace("Initialized SDL joystick and controller subsystems"); | |||||
} | |||||
// Setup rasterizer | |||||
rasterizer = new gl::rasterizer(); | |||||
rasterizer->context_resized(viewport_size[0], viewport_size[1]); | |||||
// Register keyboard and mouse with input device manager | |||||
device_manager.register_device(keyboard); | |||||
device_manager.register_device(mouse); | |||||
// Generate keyboard and mouse device connected events | |||||
keyboard.connect(); | |||||
mouse.connect(); | |||||
// Connect gamepads | |||||
process_events(); | |||||
} | |||||
application::~application() | |||||
{ | |||||
// Destroy the OpenGL context | |||||
SDL_GL_DeleteContext(sdl_gl_context); | |||||
// Destroy the SDL window | |||||
SDL_DestroyWindow(sdl_window); | |||||
// Shutdown SDL | |||||
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); | |||||
SDL_Quit(); | |||||
} | |||||
void application::close() | |||||
{ | |||||
closed = true; | |||||
} | |||||
void application::set_title(const std::string& title) | |||||
{ | |||||
SDL_SetWindowTitle(sdl_window, title.c_str()); | |||||
} | |||||
void application::set_cursor_visible(bool visible) | |||||
{ | |||||
SDL_ShowCursor((visible) ? SDL_ENABLE : SDL_DISABLE); | |||||
cursor_visible = visible; | |||||
} | |||||
void application::set_relative_mouse_mode(bool enabled) | |||||
{ | |||||
if (enabled) | |||||
{ | |||||
SDL_GetMouseState(&mouse_position[0], &mouse_position[1]); | |||||
SDL_ShowCursor(SDL_DISABLE); | |||||
SDL_SetRelativeMouseMode(SDL_TRUE); | |||||
} | |||||
else | |||||
{ | |||||
SDL_SetRelativeMouseMode(SDL_FALSE); | |||||
SDL_WarpMouseInWindow(sdl_window, mouse_position[0], mouse_position[1]); | |||||
if (cursor_visible) | |||||
{ | |||||
SDL_ShowCursor(SDL_ENABLE); | |||||
} | |||||
} | |||||
} | |||||
void application::resize_window(int width, int height) | |||||
{ | |||||
int x = (display_size[0] >> 1) - (width >> 1); | |||||
int y = (display_size[1] >> 1) - (height >> 1); | |||||
// Resize and center window | |||||
SDL_SetWindowPosition(sdl_window, x, y); | |||||
SDL_SetWindowSize(sdl_window, width, height); | |||||
} | |||||
void application::set_fullscreen(bool fullscreen) | |||||
{ | |||||
if (this->fullscreen != fullscreen) | |||||
{ | |||||
this->fullscreen = fullscreen; | |||||
if (fullscreen) | |||||
{ | |||||
SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN_DESKTOP); | |||||
} | |||||
else | |||||
{ | |||||
SDL_SetWindowFullscreen(sdl_window, 0); | |||||
} | |||||
} | |||||
} | |||||
void application::set_v_sync(bool v_sync) | |||||
{ | |||||
if (v_sync) | |||||
{ | |||||
debug::log::trace("Enabling adaptive v-sync..."); | |||||
if (SDL_GL_SetSwapInterval(-1) != 0) | |||||
{ | |||||
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError()); | |||||
debug::log::trace("Enabling synchronized v-sync..."); | |||||
if (SDL_GL_SetSwapInterval(1) != 0) | |||||
{ | |||||
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError()); | |||||
} | |||||
else | |||||
{ | |||||
this->v_sync = v_sync; | |||||
debug::log::debug("Enabled synchronized v-sync"); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
this->v_sync = v_sync; | |||||
debug::log::debug("Enabled adaptive v-sync"); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
debug::log::trace("Disabling v-sync..."); | |||||
if (SDL_GL_SetSwapInterval(0) != 0) | |||||
{ | |||||
debug::log::error("Failed to disable v-sync: {}", SDL_GetError()); | |||||
} | |||||
else | |||||
{ | |||||
this->v_sync = v_sync; | |||||
debug::log::debug("Disabled v-sync"); | |||||
} | |||||
} | |||||
} | |||||
void application::set_window_opacity(float opacity) | |||||
{ | |||||
SDL_SetWindowOpacity(sdl_window, opacity); | |||||
} | |||||
void application::swap_buffers() | |||||
{ | |||||
SDL_GL_SwapWindow(sdl_window); | |||||
} | |||||
void application::show_window() | |||||
{ | |||||
SDL_ShowWindow(sdl_window); | |||||
//SDL_GL_MakeCurrent(sdl_window, sdl_gl_context); | |||||
} | |||||
void application::hide_window() | |||||
{ | |||||
SDL_HideWindow(sdl_window); | |||||
} | |||||
void application::add_game_controller_mappings(const void* mappings, std::size_t size) | |||||
{ | |||||
debug::log::trace("Adding SDL game controller mappings..."); | |||||
int mapping_count = SDL_GameControllerAddMappingsFromRW(SDL_RWFromConstMem(mappings, static_cast<int>(size)), 0); | |||||
if (mapping_count == -1) | |||||
{ | |||||
debug::log::error("Failed to add SDL game controller mappings: {}", SDL_GetError()); | |||||
} | |||||
else | |||||
{ | |||||
debug::log::debug("Added {} SDL game controller mappings", mapping_count); | |||||
} | |||||
} | |||||
void application::process_events() | |||||
{ | |||||
// Active modifier keys | |||||
std::uint16_t sdl_key_mod = KMOD_NONE; | |||||
std::uint16_t modifier_keys = input::modifier_key::none; | |||||
// Mouse motion event accumulators | |||||
// bool mouse_motion = false; | |||||
// std::int32_t mouse_x; | |||||
// std::int32_t mouse_y; | |||||
// std::int32_t mouse_dx = 0; | |||||
// std::int32_t mouse_dy = 0; | |||||
SDL_Event sdl_event; | |||||
while (SDL_PollEvent(&sdl_event)) | |||||
{ | |||||
switch (sdl_event.type) | |||||
{ | |||||
[[likely]] case SDL_MOUSEMOTION: | |||||
{ | |||||
// More than one mouse motion event is often generated per frame, and may be a source of lag. | |||||
// Mouse events can be accumulated here to prevent excessive function calls and allocations | |||||
// mouse_motion = true; | |||||
// mouse_x = sdl_event.motion.x; | |||||
// mouse_y = sdl_event.motion.y; | |||||
// mouse_dx += sdl_event.motion.xrel; | |||||
// mouse_dy += sdl_event.motion.yrel; | |||||
mouse.move({sdl_event.motion.x, sdl_event.motion.y}, {sdl_event.motion.xrel, sdl_event.motion.yrel}); | |||||
break; | |||||
} | |||||
case SDL_KEYDOWN: | |||||
case SDL_KEYUP: | |||||
{ | |||||
// Get scancode of key | |||||
const input::scancode scancode = static_cast<input::scancode>(sdl_event.key.keysym.scancode); | |||||
// Rebuild modifier keys bit mask | |||||
if (sdl_event.key.keysym.mod != sdl_key_mod) | |||||
{ | |||||
sdl_key_mod = sdl_event.key.keysym.mod; | |||||
modifier_keys = input::modifier_key::none; | |||||
if (sdl_key_mod & KMOD_LSHIFT) | |||||
modifier_keys |= input::modifier_key::left_shift; | |||||
if (sdl_key_mod & KMOD_RSHIFT) | |||||
modifier_keys |= input::modifier_key::right_shift; | |||||
if (sdl_key_mod & KMOD_LCTRL) | |||||
modifier_keys |= input::modifier_key::left_ctrl; | |||||
if (sdl_key_mod & KMOD_RCTRL) | |||||
modifier_keys |= input::modifier_key::right_ctrl; | |||||
if (sdl_key_mod & KMOD_LGUI) | |||||
modifier_keys |= input::modifier_key::left_gui; | |||||
if (sdl_key_mod & KMOD_RGUI) | |||||
modifier_keys |= input::modifier_key::right_gui; | |||||
if (sdl_key_mod & KMOD_NUM) | |||||
modifier_keys |= input::modifier_key::num_lock; | |||||
if (sdl_key_mod & KMOD_CAPS) | |||||
modifier_keys |= input::modifier_key::caps_lock; | |||||
if (sdl_key_mod & KMOD_SCROLL) | |||||
modifier_keys |= input::modifier_key::scroll_lock; | |||||
if (sdl_key_mod & KMOD_MODE) | |||||
modifier_keys |= input::modifier_key::alt_gr; | |||||
} | |||||
// Determine if event was generated from a key repeat | |||||
const bool repeat = sdl_event.key.repeat > 0; | |||||
if (sdl_event.type == SDL_KEYDOWN) | |||||
{ | |||||
keyboard.press(scancode, repeat, modifier_keys); | |||||
} | |||||
else | |||||
{ | |||||
keyboard.release(scancode, repeat, modifier_keys); | |||||
} | |||||
break; | |||||
} | |||||
case SDL_MOUSEWHEEL: | |||||
{ | |||||
const float flip = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f; | |||||
mouse.scroll({sdl_event.wheel.preciseX * flip, sdl_event.wheel.preciseY * flip}); | |||||
break; | |||||
} | |||||
case SDL_MOUSEBUTTONDOWN: | |||||
{ | |||||
mouse.press(static_cast<input::mouse_button>(sdl_event.button.button)); | |||||
break; | |||||
} | |||||
case SDL_MOUSEBUTTONUP: | |||||
{ | |||||
mouse.release(static_cast<input::mouse_button>(sdl_event.button.button)); | |||||
break; | |||||
} | |||||
[[likely]] case SDL_CONTROLLERAXISMOTION: | |||||
{ | |||||
if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID) | |||||
{ | |||||
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
// Map axis position onto `[-1, 1]`. | |||||
const float position = math::map | |||||
( | |||||
static_cast<float>(sdl_event.caxis.value), | |||||
static_cast<float>(std::numeric_limits<decltype(sdl_event.caxis.value)>::min()), | |||||
static_cast<float>(std::numeric_limits<decltype(sdl_event.caxis.value)>::max()), | |||||
-1.0f, | |||||
1.0f | |||||
); | |||||
// Generate gamepad axis moved event | |||||
it->second->move(static_cast<input::gamepad_axis>(sdl_event.caxis.axis), position); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
case SDL_CONTROLLERBUTTONDOWN: | |||||
{ | |||||
if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID) | |||||
{ | |||||
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
it->second->press(static_cast<input::gamepad_button>(sdl_event.cbutton.button)); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
case SDL_CONTROLLERBUTTONUP: | |||||
{ | |||||
if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID) | |||||
{ | |||||
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
it->second->release(static_cast<input::gamepad_button>(sdl_event.cbutton.button)); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT: | |||||
{ | |||||
switch (sdl_event.window.event) | |||||
{ | |||||
case SDL_WINDOWEVENT_SIZE_CHANGED: | |||||
{ | |||||
// Query SDL window parameters | |||||
SDL_Window* sdl_window = SDL_GetWindowFromID(sdl_event.window.windowID); | |||||
const auto sdl_window_flags = SDL_GetWindowFlags(sdl_window); | |||||
int sdl_window_drawable_w = 0; | |||||
int sdl_window_drawable_h = 0; | |||||
SDL_GL_GetDrawableSize(sdl_window, &sdl_window_drawable_w, &sdl_window_drawable_h); | |||||
// Build window resized event | |||||
input::event::window_resized event; | |||||
event.window = nullptr; | |||||
event.size.x() = static_cast<std::int32_t>(sdl_event.window.data1); | |||||
event.size.y() = static_cast<std::int32_t>(sdl_event.window.data2); | |||||
event.maximized = sdl_window_flags & SDL_WINDOW_MAXIMIZED; | |||||
event.fullscreen = sdl_window_flags & SDL_WINDOW_FULLSCREEN; | |||||
event.viewport_size.x() = static_cast<std::int32_t>(sdl_window_drawable_w); | |||||
event.viewport_size.y() = static_cast<std::int32_t>(sdl_window_drawable_h); | |||||
// Update windowed size | |||||
if (!event.maximized && !event.fullscreen) | |||||
{ | |||||
windowed_size = event.size; | |||||
} | |||||
// Update GL context size | |||||
rasterizer->context_resized(event.viewport_size.x(), event.viewport_size.y()); | |||||
// Publish window resized event | |||||
window_resized_publisher.publish(event); | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT_MOVED: | |||||
{ | |||||
// Query SDL window parameters | |||||
SDL_Window* sdl_window = SDL_GetWindowFromID(sdl_event.window.windowID); | |||||
const auto sdl_window_flags = SDL_GetWindowFlags(sdl_window); | |||||
// Build window moved event | |||||
input::event::window_moved event; | |||||
event.window = nullptr; | |||||
event.position.x() = static_cast<std::int32_t>(sdl_event.window.data1); | |||||
event.position.y() = static_cast<std::int32_t>(sdl_event.window.data2); | |||||
event.maximized = sdl_window_flags & SDL_WINDOW_MAXIMIZED; | |||||
event.fullscreen = sdl_window_flags & SDL_WINDOW_FULLSCREEN; | |||||
// Update windowed position | |||||
if (!event.maximized && !event.fullscreen) | |||||
{ | |||||
windowed_position = event.position; | |||||
} | |||||
// Publish window moved event | |||||
window_moved_publisher.publish(event); | |||||
break; | |||||
} | |||||
case SDL_WINDOWEVENT_FOCUS_GAINED: | |||||
// Build and publish window focused gained event | |||||
window_focus_changed_publisher.publish({nullptr, true}); | |||||
break; | |||||
case SDL_WINDOWEVENT_FOCUS_LOST: | |||||
// Build and publish window focused lost event | |||||
window_focus_changed_publisher.publish({nullptr, false}); | |||||
break; | |||||
case SDL_WINDOWEVENT_MAXIMIZED: | |||||
// Update window maximized | |||||
maximized = true; | |||||
// Build and publish window maximized event | |||||
window_maximized_publisher.publish({nullptr}); | |||||
break; | |||||
case SDL_WINDOWEVENT_RESTORED: | |||||
// Update window maximized | |||||
maximized = false; | |||||
// Build and publish window restored event | |||||
window_restored_publisher.publish({nullptr}); | |||||
break; | |||||
case SDL_WINDOWEVENT_MINIMIZED: | |||||
// Build and publish window minimized event | |||||
window_minimized_publisher.publish({nullptr}); | |||||
break; | |||||
[[unlikely]] case SDL_WINDOWEVENT_CLOSE: | |||||
// Build and publish window closed event | |||||
window_closed_publisher.publish({nullptr}); | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
break; | |||||
} | |||||
[[unlikely]] case SDL_CONTROLLERDEVICEADDED: | |||||
{ | |||||
if (SDL_IsGameController(sdl_event.cdevice.which)) | |||||
{ | |||||
SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which); | |||||
const char* controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which); | |||||
if (sdl_controller) | |||||
{ | |||||
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
// Gamepad reconnected | |||||
debug::log::info("Reconnected gamepad \"{}\"", controller_name); | |||||
it->second->connect(); | |||||
} | |||||
else | |||||
{ | |||||
// Get gamepad GUID | |||||
SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller); | |||||
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick); | |||||
// Copy into UUID struct | |||||
::uuid gamepad_uuid; | |||||
std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size()); | |||||
debug::log::info("Connected gamepad \"{}\" with UUID {}", controller_name, gamepad_uuid.string()); | |||||
// Create new gamepad | |||||
input::gamepad* gamepad = new input::gamepad(); | |||||
gamepad->set_uuid(gamepad_uuid); | |||||
// Add gamepad to gamepad map | |||||
gamepad_map[sdl_event.cdevice.which] = gamepad; | |||||
// Register gamepad with device manager | |||||
device_manager.register_device(*gamepad); | |||||
// Generate gamepad connected event | |||||
gamepad->connect(); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
debug::log::error("Failed to connected gamepad \"{}\": {}", controller_name, SDL_GetError()); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
[[unlikely]] case SDL_CONTROLLERDEVICEREMOVED: | |||||
{ | |||||
SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which); | |||||
if (sdl_controller) | |||||
{ | |||||
const char* controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which); | |||||
SDL_GameControllerClose(sdl_controller); | |||||
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end()) | |||||
{ | |||||
it->second->disconnect(); | |||||
} | |||||
debug::log::info("Disconnected gamepad \"{}\"", controller_name); | |||||
} | |||||
break; | |||||
} | |||||
[[unlikely]] case SDL_QUIT: | |||||
{ | |||||
debug::log::info("Quit requested"); | |||||
close(); | |||||
break; | |||||
} | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
// Process accumulated mouse motion events | |||||
// if (mouse_motion) | |||||
// { | |||||
// mouse.move(mouse_x, mouse_y, mouse_dx, mouse_dy); | |||||
// } | |||||
} |
@ -1,295 +0,0 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_APPLICATION_HPP | |||||
#define ANTKEEPER_APPLICATION_HPP | |||||
#include "event/publisher.hpp" | |||||
#include "gl/rasterizer.hpp" | |||||
#include "input/device-manager.hpp" | |||||
#include "input/event.hpp" | |||||
#include "input/gamepad.hpp" | |||||
#include "input/keyboard.hpp" | |||||
#include "input/mouse.hpp" | |||||
#include "utility/fundamental-types.hpp" | |||||
#include <list> | |||||
#include <memory> | |||||
#include <unordered_map> | |||||
// Forward declarations | |||||
typedef struct SDL_Window SDL_Window; | |||||
typedef void* SDL_GLContext; | |||||
/** | |||||
* | |||||
*/ | |||||
class application | |||||
{ | |||||
public: | |||||
/** | |||||
* Constructs and initializes an application. | |||||
*/ | |||||
application | |||||
( | |||||
const std::string& window_title, | |||||
int window_x, | |||||
int window_y, | |||||
int window_w, | |||||
int window_h, | |||||
bool maximized, | |||||
bool fullscreen, | |||||
bool v_sync | |||||
); | |||||
/** | |||||
* Destructs an application. | |||||
*/ | |||||
~application(); | |||||
/** | |||||
* Requests the application to close. | |||||
*/ | |||||
void close(); | |||||
/** | |||||
* Sets the application window's title. | |||||
* | |||||
* @param title Window title. | |||||
*/ | |||||
void set_title(const std::string& title); | |||||
/** | |||||
* Sets the cursor visibility. | |||||
* | |||||
* @param visible `true` if the cursor should be visible, `false` otherwise. | |||||
*/ | |||||
void set_cursor_visible(bool visible); | |||||
/** | |||||
* Enables or disables relative mouse mode, in which only relative mouse movement events are generated. | |||||
* | |||||
* @param enabled `true` if relative mouse mode should be enabled, `false` otherwise. | |||||
*/ | |||||
void set_relative_mouse_mode(bool enabled); | |||||
/** | |||||
* Resizes the application window. | |||||
* | |||||
* @param width Width of the window, in pixels. | |||||
* @param height Height of the window, in pixels. | |||||
*/ | |||||
void resize_window(int width, int height); | |||||
/** | |||||
* Puts the application window into either fullscreen or window mode. | |||||
* | |||||
* @param fullscreen `true` if the window should be fullscreen, `false` if it should be window. | |||||
*/ | |||||
void set_fullscreen(bool fullscreen); | |||||
/** | |||||
* Enables or disables v-sync mode. | |||||
* | |||||
* @param v_sync `true` if v-sync should be enabled, `false` otherwise. | |||||
*/ | |||||
void set_v_sync(bool v_sync); | |||||
void set_window_opacity(float opacity); | |||||
void swap_buffers(); | |||||
void show_window(); | |||||
void hide_window(); | |||||
void add_game_controller_mappings(const void* mappings, std::size_t size); | |||||
/// Returns the dimensions of the current display. | |||||
[[nodiscard]] const int2& get_display_size() const; | |||||
/// Returns the DPI of the display. | |||||
[[nodiscard]] float get_display_dpi() const; | |||||
/// Returns the position of the window when not maximized or fullscreen. | |||||
[[nodiscard]] const int2& get_windowed_position() const; | |||||
/// Returns the dimensions of the window when not maximized or fullscreen. | |||||
[[nodiscard]] const int2& get_windowed_size() const; | |||||
/// Returns the dimensions of the window's drawable viewport. | |||||
[[nodiscard]] const int2& get_viewport_size() const; | |||||
/// Returns `true` if the window is maximized, `false` otherwise. | |||||
[[nodiscard]] bool is_maximized() const; | |||||
/// Returns `true` if the window is in fullscreen mode, `false` otherwise. | |||||
[[nodiscard]] bool is_fullscreen() const; | |||||
/// Returns `true` if the v-sync is enabled, `false` otherwise. | |||||
[[nodiscard]] bool get_v_sync() const; | |||||
/// Returns the rasterizer for the window. | |||||
[[nodiscard]] gl::rasterizer* get_rasterizer(); | |||||
void process_events(); | |||||
[[nodiscard]] bool was_closed() const; | |||||
/** | |||||
* Returns the input device manager. | |||||
*/ | |||||
/// @{ | |||||
[[nodiscard]] inline const input::device_manager& get_device_manager() const noexcept | |||||
{ | |||||
return device_manager; | |||||
} | |||||
[[nodiscard]] inline input::device_manager& get_device_manager() noexcept | |||||
{ | |||||
return device_manager; | |||||
} | |||||
/// @} | |||||
/// Returns the channel through which window closed events are published. | |||||
[[nodiscard]] inline event::channel<input::event::window_closed>& get_window_closed_channel() noexcept | |||||
{ | |||||
return window_closed_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window focus changed events are published. | |||||
[[nodiscard]] inline event::channel<input::event::window_focus_changed>& get_window_focus_changed_channel() noexcept | |||||
{ | |||||
return window_focus_changed_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window moved events are published. | |||||
[[nodiscard]] inline event::channel<input::event::window_moved>& get_window_moved_channel() noexcept | |||||
{ | |||||
return window_moved_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window resized events are published. | |||||
[[nodiscard]] inline event::channel<input::event::window_resized>& get_window_resized_channel() noexcept | |||||
{ | |||||
return window_resized_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window maximized events are published. | |||||
[[nodiscard]] inline event::channel<input::event::window_maximized>& get_window_maximized_channel() noexcept | |||||
{ | |||||
return window_maximized_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window restored events are published. | |||||
[[nodiscard]] inline event::channel<input::event::window_restored>& get_window_restored_channel() noexcept | |||||
{ | |||||
return window_restored_publisher.channel(); | |||||
} | |||||
/// Returns the channel through which window minimized events are published. | |||||
[[nodiscard]] inline event::channel<input::event::window_minimized>& get_window_minimized_channel() noexcept | |||||
{ | |||||
return window_minimized_publisher.channel(); | |||||
} | |||||
private: | |||||
void window_moved(); | |||||
void window_resized(); | |||||
bool closed; | |||||
bool maximized; | |||||
bool fullscreen; | |||||
bool v_sync; | |||||
bool cursor_visible; | |||||
int2 display_size; | |||||
float display_dpi; | |||||
int2 windowed_position; | |||||
int2 windowed_size; | |||||
int2 viewport_size; | |||||
int2 mouse_position; | |||||
SDL_Window* sdl_window; | |||||
SDL_GLContext sdl_gl_context; | |||||
gl::rasterizer* rasterizer; | |||||
// Input devices | |||||
input::device_manager device_manager; | |||||
input::keyboard keyboard; | |||||
input::mouse mouse; | |||||
std::unordered_map<int, input::gamepad*> gamepad_map; | |||||
event::publisher<input::event::window_closed> window_closed_publisher; | |||||
event::publisher<input::event::window_focus_changed> window_focus_changed_publisher; | |||||
event::publisher<input::event::window_moved> window_moved_publisher; | |||||
event::publisher<input::event::window_resized> window_resized_publisher; | |||||
event::publisher<input::event::window_maximized> window_maximized_publisher; | |||||
event::publisher<input::event::window_restored> window_restored_publisher; | |||||
event::publisher<input::event::window_minimized> window_minimized_publisher; | |||||
}; | |||||
inline const int2& application::get_display_size() const | |||||
{ | |||||
return display_size; | |||||
} | |||||
inline float application::get_display_dpi() const | |||||
{ | |||||
return display_dpi; | |||||
} | |||||
inline const int2& application::get_windowed_position() const | |||||
{ | |||||
return windowed_position; | |||||
} | |||||
inline const int2& application::get_windowed_size() const | |||||
{ | |||||
return windowed_size; | |||||
} | |||||
inline const int2& application::get_viewport_size() const | |||||
{ | |||||
return viewport_size; | |||||
} | |||||
inline bool application::is_maximized() const | |||||
{ | |||||
return maximized; | |||||
} | |||||
inline bool application::is_fullscreen() const | |||||
{ | |||||
return fullscreen; | |||||
} | |||||
inline bool application::get_v_sync() const | |||||
{ | |||||
return v_sync; | |||||
} | |||||
inline gl::rasterizer* application::get_rasterizer() | |||||
{ | |||||
return rasterizer; | |||||
} | |||||
inline bool application::was_closed() const | |||||
{ | |||||
return closed; | |||||
} | |||||
#endif // ANTKEEPER_APPLICATION_HPP |
@ -1,121 +0,0 @@ | |||||
/* | |||||
* Copyright (C) 2023 Christopher J. Howard | |||||
* | |||||
* This file is part of Antkeeper source code. | |||||
* | |||||
* Antkeeper source code is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 3 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* Antkeeper source code is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "input/device-manager.hpp" | |||||
namespace input { | |||||
void device_manager::register_device(device& device) | |||||
{ | |||||
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue)); | |||||
switch (device.get_device_type()) | |||||
{ | |||||
case device_type::gamepad: | |||||
register_gamepad(static_cast<gamepad&>(device)); | |||||
break; | |||||
case device_type::keyboard: | |||||
register_keyboard(static_cast<keyboard&>(device)); | |||||
break; | |||||
case device_type::mouse: | |||||
register_mouse(static_cast<mouse&>(device)); | |||||
break; | |||||
default: | |||||
//std::unreachable(); | |||||
break; | |||||
} | |||||
} | |||||
void device_manager::unregister_device(device& device) | |||||
{ | |||||
subscriptions.erase(&device); | |||||
switch (device.get_device_type()) | |||||
{ | |||||
case device_type::gamepad: | |||||
unregister_gamepad(static_cast<gamepad&>(device)); | |||||
break; | |||||
case device_type::keyboard: | |||||
unregister_keyboard(static_cast<keyboard&>(device)); | |||||
break; | |||||
case device_type::mouse: | |||||
unregister_mouse(static_cast<mouse&>(device)); | |||||
break; | |||||
default: | |||||
//std::unreachable(); | |||||
break; | |||||
} | |||||
} | |||||
void device_manager::register_gamepad(gamepad& gamepad) | |||||
{ | |||||
// Connect gamepad event signals to the event queue | |||||
subscriptions.emplace(&gamepad, gamepad.get_axis_moved_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&gamepad, gamepad.get_button_pressed_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&gamepad, gamepad.get_button_released_channel().subscribe(event_queue)); | |||||
// Add gamepad to list of gamepads | |||||
gamepads.emplace(&gamepad); | |||||
} | |||||
void device_manager::register_keyboard(keyboard& keyboard) | |||||
{ | |||||
// Connect keyboard event signals to the event queue | |||||
subscriptions.emplace(&keyboard, keyboard.get_key_pressed_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&keyboard, keyboard.get_key_released_channel().subscribe(event_queue)); | |||||
// Add keyboard to list of keyboards | |||||
keyboards.emplace(&keyboard); | |||||
} | |||||
void device_manager::register_mouse(mouse& mouse) | |||||
{ | |||||
// Connect mouse event signals to the event queue | |||||
subscriptions.emplace(&mouse, mouse.get_button_pressed_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&mouse, mouse.get_button_released_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&mouse, mouse.get_moved_channel().subscribe(event_queue)); | |||||
subscriptions.emplace(&mouse, mouse.get_scrolled_channel().subscribe(event_queue)); | |||||
// Add mouse to list of mice | |||||
mice.emplace(&mouse); | |||||
} | |||||
void device_manager::unregister_gamepad(gamepad& gamepad) | |||||
{ | |||||
gamepads.erase(&gamepad); | |||||
} | |||||
void device_manager::unregister_keyboard(keyboard& keyboard) | |||||
{ | |||||
keyboards.erase(&keyboard); | |||||
} | |||||
void device_manager::unregister_mouse(mouse& mouse) | |||||
{ | |||||
mice.erase(&mouse); | |||||
} | |||||
} // namespace input |