@ -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 |