diff --git a/.github/workflows/build-system.yml b/.github/workflows/build-system.yml index 76bf690da..51a43c437 100644 --- a/.github/workflows/build-system.yml +++ b/.github/workflows/build-system.yml @@ -89,6 +89,7 @@ jobs: runs-on: ubuntu-latest permissions: checks: write + pull-requests: write if: always() steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index 54e5b9fa2..80901d84e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,7 @@ add_subdirectory(thirdparty) add_subdirectory(resources) add_subdirectory(graphics) add_subdirectory(audio) +add_subdirectory(input) add_subdirectory(src) if(NOVELRT_BUILD_SAMPLES) diff --git a/include/NovelRT/Ecs/Input/Ecs.Input.h b/include/NovelRT/Ecs/Input/Ecs.Input.h index 5c1311fb7..489a5bf67 100644 --- a/include/NovelRT/Ecs/Input/Ecs.Input.h +++ b/include/NovelRT/Ecs/Input/Ecs.Input.h @@ -8,7 +8,11 @@ #error NovelRT does not support including types explicitly by default. Please include Ecs.h instead for the Ecs namespace subset. #endif -#include "../../Input/Input.h" +#include +#include +#include +#include +#include #include namespace NovelRT::Ecs::Input diff --git a/include/NovelRT/Exceptions/Exceptions.h b/include/NovelRT/Exceptions/Exceptions.h index 67c807834..c63b958b9 100644 --- a/include/NovelRT/Exceptions/Exceptions.h +++ b/include/NovelRT/Exceptions/Exceptions.h @@ -1,6 +1,5 @@ -// -// Created by Matt on 06/01/2021. -// +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. #ifndef NOVELRT_EXCEPTIONS_H #define NOVELRT_EXCEPTIONS_H @@ -17,7 +16,6 @@ #include "NotInitialisedException.h" #include "NotSupportedException.h" #include "NullPointerException.h" -#include "OpenGLLinkageFailureException.h" #include "OutOfMemoryException.h" #include "RuntimeNotFoundException.h" #include "TimeoutException.h" diff --git a/include/NovelRT/Exceptions/OpenGLLinkageFailureException.h b/include/NovelRT/Exceptions/OpenGLLinkageFailureException.h deleted file mode 100644 index b5c4fd017..000000000 --- a/include/NovelRT/Exceptions/OpenGLLinkageFailureException.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_EXCEPTIONS_OPENGLLINKAGEFAILUREEXCEPTION_H -#define NOVELRT_EXCEPTIONS_OPENGLLINKAGEFAILUREEXCEPTION_H - -#include -#include - -namespace NovelRT::Exceptions -{ - class OpenGLLinkageFailureException final : public std::runtime_error - { - public: - OpenGLLinkageFailureException(uint32_t programId, const std::string& errorMessage) - : std::runtime_error(std::string("Program with ID \"") + std::to_string(programId) + - "\" has encountered an error. Error: " + errorMessage) - { - } - }; -} - -#endif //! NOVELRT_EXCEPTIONS_OPENGLLINKAGEFAILUREEXCEPTION_H \ No newline at end of file diff --git a/include/NovelRT/Input/Glfw/Input.Glfw.h b/include/NovelRT/Input/Glfw/Input.Glfw.h deleted file mode 100644 index 0826e3072..000000000 --- a/include/NovelRT/Input/Glfw/Input.Glfw.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_INPUT_GLFW_H -#define NOVELRT_INPUT_GLFW_H - -/** - * @brief The default GLFW3 implementation of the Input plugin API. - */ -namespace NovelRT::Input::Glfw -{ - class GlfwInputDevice; -} - -// Input.Glfw dependencies -#define GLFW_INCLUDE_NONE -#include "NovelRT/Input/Input.h" -#include "NovelRT/PluginManagement/PluginManagement.h" -#include -#include - -// Input.Glfw types -#include "GlfwInputDevice.h" -#include "GlfwInputPluginProvider.h" - -#endif // NOVELRT_INPUT_GLFW_H diff --git a/include/NovelRT/Input/Input.h b/include/NovelRT/Input/Input.h deleted file mode 100644 index ff7bcd03d..000000000 --- a/include/NovelRT/Input/Input.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_INPUT_H -#define NOVELRT_INPUT_H - -// Input Dependencies -#include "NovelRT/LoggingService.h" -#include "NovelRT/Maths/Maths.h" -#include "NovelRT/Timing/Timestamp.h" -#include "NovelRT/Utilities/Misc.h" -#include -#include - -/** - * @brief The input plugin API. - */ -namespace NovelRT::Input -{ - enum class KeyState; - class IInputDevice; - class NovelKey; - struct InputAction; -} - -// clang-format off -// Input Types -#include "IInputDevice.h" -#include "KeyState.h" -#include "NovelKey.h" -#include "InputAction.h" -// clang-format on - -#endif // NOVELRT_INPUT_H diff --git a/include/NovelRT/Input/KeyState.h b/include/NovelRT/Input/KeyState.h deleted file mode 100644 index 14b33754e..000000000 --- a/include/NovelRT/Input/KeyState.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_INPUT_KEYSTATE_H -#define NOVELRT_INPUT_KEYSTATE_H - -#ifndef NOVELRT_INPUT_H -#error NovelRT does not support including types explicitly by default. Please include Input.h instead for the Input namespace subset. -#endif - -namespace NovelRT::Input -{ - /** - * @brief A set of key states. - */ - enum class KeyState : int32_t - { - Idle = 0, - KeyUp = 1, - KeyDown = 2, - KeyDownHeld = 3 - }; -} - -#endif // !NOVELRT_INPUT_KEYSTATE_H diff --git a/include/NovelRT/NovelRT.h b/include/NovelRT/NovelRT.h index e0f4156e3..ac18b769e 100644 --- a/include/NovelRT/NovelRT.h +++ b/include/NovelRT/NovelRT.h @@ -114,8 +114,6 @@ #include #include #include - #include - #include // Plugin Management types #include diff --git a/include/NovelRT/PluginManagement/PluginManagement.h b/include/NovelRT/PluginManagement/PluginManagement.h index 1362b2839..61bb4c003 100644 --- a/include/NovelRT/PluginManagement/PluginManagement.h +++ b/include/NovelRT/PluginManagement/PluginManagement.h @@ -5,10 +5,10 @@ #define NOVELRT_PLUGINMANAGEMENT_H // PluginManagement Dependencies -#include "../Input/Input.h" #include "../ResourceManagement/ResourceManagement.h" #include "../Windowing/Windowing.h" #include +#include /** * @brief The NovelRT engine plugin system for loading modules such as Vulkan, GLFW3, OpenAL, and more. diff --git a/include/NovelRT/Utilities/Misc.h b/include/NovelRT/Utilities/Misc.h index ce4b25d64..267078dfd 100644 --- a/include/NovelRT/Utilities/Misc.h +++ b/include/NovelRT/Utilities/Misc.h @@ -43,6 +43,7 @@ namespace NovelRT::Utilities static inline const char* CONSOLE_LOG_AUDIO = "Audio"; static inline const char* CONSOLE_LOG_INPUT = "Input"; static inline const char* CONSOLE_LOG_WINDOWING = "WindowManager"; + static inline const char* CONSOLE_LOG_ECS_INPUT = "EcsInputSystem"; template #ifdef NOVELRT_USE_STD_SPAN diff --git a/include/NovelRT/Windowing/Glfw/GlfwWindowingDevice.h b/include/NovelRT/Windowing/Glfw/GlfwWindowingDevice.h index 49fa3951a..f50039a71 100644 --- a/include/NovelRT/Windowing/Glfw/GlfwWindowingDevice.h +++ b/include/NovelRT/Windowing/Glfw/GlfwWindowingDevice.h @@ -12,6 +12,23 @@ namespace NovelRT::Windowing::Glfw { class GlfwWindowingDevice final : public IWindowingDevice { + public: + struct CursorPositionEventArgs + { + double x = 0; + double y = 0; + }; + + struct ButtonChangeEventArgs + { + int32_t key = 0; + int32_t action = 0; + }; + + Utilities::Event CursorMoved; + Utilities::Event MouseButtonClicked; + Utilities::Event KeyboardButtonChanged; + private: std::unique_ptr _window; std::string _currentTitle; diff --git a/input/CMakeLists.txt b/input/CMakeLists.txt new file mode 100644 index 000000000..51126a07a --- /dev/null +++ b/input/CMakeLists.txt @@ -0,0 +1,67 @@ +add_library(NovelRT-Input STATIC + KeyStateFrameChangeLog.cpp + NovelKey.cpp + Glfw/GlfwInputDevice.cpp + Glfw/GlfwInputPluginProvider.cpp +) + +target_sources(NovelRT-Input + PUBLIC + FILE_SET public_headers + TYPE HEADERS + BASE_DIRS include + FILES + include/NovelRT/Input/IInputDevice.hpp + include/NovelRT/Input/InputAction.hpp + include/NovelRT/Input/KeyState.hpp + include/NovelRT/Input/KeyStateFrameChangeLog.hpp + include/NovelRT/Input/NovelKey.hpp + include/NovelRT/Input/Glfw/GlfwInputDevice.hpp + include/NovelRT/Input/Glfw/GlfwInputPluginProvider.hpp +) + +set_target_properties(NovelRT-Input + PROPERTIES + EXPORT_NAME Input + POSITION_INDEPENDENT_CODE ON +) + +target_compile_features(NovelRT-Input + PUBLIC + cxx_std_17 +) + +target_compile_options(NovelRT-Input + PRIVATE + $<$:-Wall> + $<$:-Wabi> + $<$:-Werror> + $<$:-Wextra> + $<$:-Wpedantic> + $<$:-pedantic-errors> + + $<$:-Wall> + $<$:-Werror> + $<$:-Wextra> + $<$:-Wpedantic> + $<$:-pedantic-errors> + + $<$:/W4> + $<$:/WX> + $<$:/permissive-> +) + +target_include_directories(NovelRT-Input PRIVATE "$") +target_link_libraries(NovelRT-Input + PUBLIC + spdlog + glfw + tbb +) + +install( + TARGETS NovelRT-Input + EXPORT NovelRTConfig + LIBRARY DESTINATION lib + FILE_SET public_headers DESTINATION include +) diff --git a/src/NovelRT/Input/Glfw/GlfwInputDevice.cpp b/input/Glfw/GlfwInputDevice.cpp similarity index 60% rename from src/NovelRT/Input/Glfw/GlfwInputDevice.cpp rename to input/Glfw/GlfwInputDevice.cpp index c0e885376..4627ce5a8 100644 --- a/src/NovelRT/Input/Glfw/GlfwInputDevice.cpp +++ b/input/Glfw/GlfwInputDevice.cpp @@ -1,7 +1,8 @@ // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#include +#include +#include namespace NovelRT::Input::Glfw { @@ -10,9 +11,10 @@ namespace NovelRT::Input::Glfw _previousStates(std::vector()), _mousePos(NovelRT::Maths::GeoVector2F::Zero()) { + _logger = NovelRT::LoggingService(NovelRT::Utilities::Misc::CONSOLE_LOG_INPUT); } - void GlfwInputDevice::Initialise(void* window) + void GlfwInputDevice::Initialise(std::shared_ptr device) { if (!glfwInit()) { @@ -21,18 +23,381 @@ namespace NovelRT::Input::Glfw throw Exceptions::InitialisationFailureException("GLFW3 failed to initialise.", std::string(output)); } - if (window == nullptr) + if (device == nullptr) { throw Exceptions::NullPointerException( - "Could not initialise GLFW input service - null pointer was provided for window."); + "Could not initialise GLFW input service - null pointer was provided for windowing device."); } _logger.logInfoLine("Initialising GLFW input service."); _availableKeys = std::map(); _mappedActions = std::vector(); - _window = reinterpret_cast(window); + _inputBufferCount = DEFAULT_INPUT_BUFFER_COUNT; + _keyStates = std::vector>(_inputBufferCount); + _previousBufferIndex = 0; + _currentBufferIndex = 1; + + auto properDevice = std::reinterpret_pointer_cast(device); + _window = properDevice->GetRawGLFWwindowHandle(); + properDevice->KeyboardButtonChanged += + [this](auto eventArgs) { ProcessKeyInput(eventArgs.key, eventArgs.action); }; + properDevice->MouseButtonClicked += + [this](auto eventArgs) { ProcessKeyInput(eventArgs.key, eventArgs.action); }; + properDevice->CursorMoved += [this](auto eventArgs) { + NovelRT::Maths::GeoVector2F nativePos = NovelRT::Maths::GeoVector2F(static_cast(eventArgs.x), static_cast(eventArgs.y)); + ProcessCursorMovement(nativePos); + }; + + MapAllGlfwKeysToNovelKeys(); + _isInitialised = true; + + int32_t width = 0; + int32_t height = 0; + glfwGetWindowSize(_window, &width, &height); + _windowDimensions = NovelRT::Maths::GeoVector2F(static_cast(width), static_cast(height)); + + _logger.logInfo("GLFW input system initialised: window at {} x {}", width, height); + } + + void GlfwInputDevice::Update(Timing::Timestamp /*delta*/) + { + double x = 0; + double y = 0; + int32_t width = 0; + int32_t height = 0; + glfwGetWindowSize(_window, &width, &height); + glfwGetCursorPos(_window, &x, &y); + _windowDimensions.x = static_cast(width); + _windowDimensions.y =static_cast(height); + + auto& currentBuffer = _keyStates.at(_currentBufferIndex); + + for (const auto& [key, log] : _keyStates.at(_previousBufferIndex)) + { + auto findResultForCurrent = currentBuffer.find(key); + if (findResultForCurrent != currentBuffer.end()) + { + ProcessKeyState(findResultForCurrent->first, findResultForCurrent->second.GetCurrentState()); + } + else + { + ProcessKeyState(key, log.GetCurrentState()); + } + } + } - // Map GLFW keys to NovelKeys + KeyState GlfwInputDevice::GetKeyState(const std::string& key) noexcept + { + for (auto& action : _mappedActions) + { + if (action.actionName != key) + { + continue; + } + + auto externalKeyCode = action.pairedKey.GetExternalKeyCode(); + + auto& currentBuffer = _keyStates.at(_currentBufferIndex); + for (const auto& [currentKey, currentLog] : currentBuffer) + { + if (currentKey == externalKeyCode) + { + return currentLog.GetCurrentState(); + } + } + } + + return KeyState::Idle; + } + + bool GlfwInputDevice::IsKeyPressed(const std::string& input) noexcept + { + for (auto& action : _mappedActions) + { + if (action.actionName != input) + { + continue; + } + + auto key = action.pairedKey.GetExternalKeyCode(); + auto& currentBuffer = _keyStates.at(_currentBufferIndex); + for (auto& [currentKey, currentLog] : currentBuffer) + { + if (currentKey != key) + { + continue; + } + + return currentLog.GetCurrentState() == KeyState::KeyDown; + } + } + + _logger.logWarning("Requested action is not mapped: {}", input); + return false; + } + + bool GlfwInputDevice::IsKeyHeld(const std::string& input) noexcept + { + for (auto& action : _mappedActions) + { + if (action.actionName != input) + { + continue; + } + + auto key = action.pairedKey.GetExternalKeyCode(); + auto& currentBuffer = _keyStates.at(_currentBufferIndex); + for (auto& [currentKey, currentLog] : currentBuffer) + { + if (currentKey != key) + { + continue; + } + + return currentLog.GetCurrentState() == KeyState::KeyDownHeld; + } + } + + _logger.logWarning("Requested action is not mapped: {}", input); + return false; + } + + bool GlfwInputDevice::IsKeyReleased(const std::string& input) noexcept + { + for (auto& action : _mappedActions) + { + if (action.actionName != input) + { + continue; + } + + auto key = action.pairedKey.GetExternalKeyCode(); + auto& currentBuffer = _keyStates.at(_currentBufferIndex); + for (auto& [currentKey, currentLog] : currentBuffer) + { + if (currentKey != key) + { + continue; + } + + return currentLog.GetCurrentState() == KeyState::KeyUp; + } + } + + _logger.logWarning("Requested action is not mapped: {}", input); + return false; + } + + InputAction& GlfwInputDevice::AddInputAction(const std::string& actionName, const std::string& keyIdentifier) + { + bool nameExists = false; + bool keyBoundAlready = false; + NovelKey pairedKey = GetAvailableKey(keyIdentifier); + + for (auto existingAction : _mappedActions) + { + if (existingAction.actionName == actionName) + { + nameExists = true; + } + if (existingAction.pairedKey == pairedKey) + { + keyBoundAlready = true; + } + } + + if (nameExists) + { + throw Exceptions::InvalidOperationException("Cannot add InputAction as the name has already been mapped!"); + } + else if (keyBoundAlready) + { + throw Exceptions::InvalidOperationException("Cannot add InputAction as key has already been bound!"); + } + else + { + _mappedActions.emplace_back(InputAction{actionName, pairedKey}); + return _mappedActions.back(); + } + } + + NovelKey& GlfwInputDevice::GetAvailableKey(const std::string& keyRequested) + { + if (keyRequested == "") + { + throw NovelRT::Exceptions::InvalidOperationException("Cannot request a key with no name."); + } + + for (auto key : _availableKeys) + { + if (key.first == keyRequested) + { + return _availableKeys.at(key.first); + } + } + + _logger.logError("Key {} not available from this input service.", keyRequested); + throw NovelRT::Exceptions::KeyNotFoundException("Unavailable input key requested from input service."); + } + + NovelRT::Utilities::Misc::Span GlfwInputDevice::GetAllMappings() noexcept + { + return _mappedActions; + } + + NovelRT::Maths::GeoVector2F GlfwInputDevice::GetMousePosition() noexcept + { + return _mousePos; + } + + void GlfwInputDevice::ProcessKeyInput(int32_t key, int32_t state) + { + auto& map = _keyStates.at(_currentBufferIndex); + + for (auto& mapped : _mappedActions) + { + if (mapped.pairedKey.GetExternalKeyCode() != key) + { + continue; + } + + KeyStateFrameChangeLog log{}; + bool mouseMod = false; + for (auto& [currentKey, currentLog] : map) + { + if (currentKey != mapped.pairedKey.GetExternalKeyCode()) + { + continue; + } + log = currentLog; + if (currentKey == GLFW_MOUSE_BUTTON_LEFT || currentKey == GLFW_MOUSE_BUTTON_MIDDLE || + currentKey == GLFW_MOUSE_BUTTON_RIGHT) + { + mouseMod = true; + } + break; + } + + if (mouseMod) + { + auto currentState = log.GetCurrentState(); + if (state == (int32_t)KeyState::KeyDown && + (currentState == KeyState::KeyDown || currentState == KeyState::KeyDownHeld)) + { + state = (int32_t)KeyState::KeyDownHeld; + } + } + + log.PushNewState(static_cast(state)); + map.insert_or_assign(key, log); + } + } + + void GlfwInputDevice::ProcessCursorMovement(NovelRT::Maths::GeoVector2F& pos) + { + _mousePos = DetermineMouseScreenPosition(pos); + } + + void GlfwInputDevice::ProcessKeyState(int32_t key, KeyState state) + { + auto& previousBuffer = _keyStates.at(_previousBufferIndex); + auto& currentBuffer = _keyStates.at(_currentBufferIndex); + + KeyState previousStateResult = KeyState::Idle; + + for (auto& [previousKey, previousLog] : previousBuffer) + { + if (previousKey == key) + { + previousStateResult = previousLog.GetCurrentState(); + break; + } + } + + KeyStateFrameChangeLog changeLogObject = KeyStateFrameChangeLog(); + + for (auto& [currentKey, currentLog] : currentBuffer) + { + if (currentKey == key) + { + changeLogObject = currentLog; + break; + } + } + + switch (state) + { + case KeyState::KeyDown: + { + if (previousStateResult == KeyState::KeyDown) + { + changeLogObject.PushNewState(KeyState::KeyDownHeld); + } + else if (previousStateResult != KeyState::KeyDownHeld) + { + changeLogObject.PushNewState(KeyState::KeyDown); + } + break; + } + case KeyState::KeyDownHeld: + case KeyState::KeyUp: + { + changeLogObject.PushNewState((previousStateResult == KeyState::KeyUp) ? KeyState::Idle : state); + break; + } + case KeyState::Idle: + default: + { + break; + } + } + + currentBuffer.insert_or_assign(key, changeLogObject); + } + + NovelRT::Maths::GeoVector2F GlfwInputDevice::DetermineMouseScreenPosition(NovelRT::Maths::GeoVector2F& pos) + { + return NovelRT::Maths::GeoVector2F(static_cast(pos.x - (_windowDimensions.x / 2)), + static_cast(-pos.y + (_windowDimensions.y / 2))); + } + + KeyStateFrameChangeLog GlfwInputDevice::GetCurrentChangeLog(const std::string& key) + { + for (auto& action : _mappedActions) + { + if (action.actionName != key) + { + continue; + } + + return _keyStates.at(_currentBufferIndex).at(action.pairedKey.GetExternalKeyCode()); + } + + return KeyStateFrameChangeLog(); + } + + KeyStateFrameChangeLog GlfwInputDevice::GetPreviousChangeLog(const std::string& key) + { + for (auto& action : _mappedActions) + { + if (action.actionName != key) + { + continue; + } + + return _keyStates.at(_previousBufferIndex).at(action.pairedKey.GetExternalKeyCode()); + } + + return KeyStateFrameChangeLog(); + } + + NovelRT::Utilities::Misc::Span> GlfwInputDevice::GetAllChangeLogs() + { + return _keyStates; + } + + void GlfwInputDevice::MapAllGlfwKeysToNovelKeys() + { _availableKeys.emplace("LeftMouseButton", NovelKey("LeftMouseButton", GLFW_MOUSE_BUTTON_LEFT)); _availableKeys.emplace("RightMouseButton", NovelKey("RightMouseButton", GLFW_MOUSE_BUTTON_RIGHT)); _availableKeys.emplace("MiddleMouseButton", NovelKey("MiddleMouseButton", GLFW_MOUSE_BUTTON_MIDDLE)); @@ -161,194 +526,6 @@ namespace NovelRT::Input::Glfw _availableKeys.emplace("Quote", NovelKey("Quote", GLFW_KEY_APOSTROPHE, GLFW_MOD_SHIFT)); _availableKeys.emplace("Underscore", NovelKey("Underscore", GLFW_KEY_MINUS, GLFW_MOD_SHIFT)); _availableKeys.emplace("Tilde", NovelKey("Tilde", GLFW_KEY_GRAVE_ACCENT, GLFW_MOD_SHIFT)); - - _isInitialised = true; - - int width = 0; - int height = 0; - glfwGetWindowSize(_window, &width, &height); - _logger.logInfo("GLFW input system initialised: window at {} x {}", width, height); - } - - void GlfwInputDevice::Update(Timing::Timestamp /*delta*/) - { - double x = 0; - double y = 0; - int width = 0; - int height = 0; - glfwGetCursorPos(_window, &x, &y); - glfwGetWindowSize(_window, &width, &height); - _mousePos.x = static_cast(x - (width / 2)); - _mousePos.y = static_cast(-y + (height / 2)); - - _previousStates = _mappedActions; - - size_t count = _mappedActions.size(); - auto mapIterator = std::next(_mappedActions.begin(), 0); - auto stateIterator = std::next(_previousStates.begin(), 0); - - for (size_t c = 0; c < count; c++) - { - mapIterator = std::next(_mappedActions.begin(), c); - stateIterator = std::next(_previousStates.begin(), c); - - if (mapIterator->actionName == stateIterator->actionName) - { - bool press = false; - bool release = false; - - if ((mapIterator->pairedKey.GetKeyName() == "LeftMouseButton") || - (mapIterator->pairedKey.GetKeyName() == "RightMouseButton") || - (mapIterator->pairedKey.GetKeyName() == "MiddleMouseButton")) - { - press = glfwGetMouseButton(_window, mapIterator->pairedKey.GetExternalKeyCode()) == GLFW_PRESS; - release = glfwGetMouseButton(_window, mapIterator->pairedKey.GetExternalKeyCode()) == GLFW_RELEASE; - } - else - { - press = glfwGetKey(_window, mapIterator->pairedKey.GetExternalKeyCode()) == GLFW_PRESS; - release = glfwGetKey(_window, mapIterator->pairedKey.GetExternalKeyCode()) == GLFW_RELEASE; - } - - if (press && - (stateIterator->state == KeyState::KeyDown || stateIterator->state == KeyState::KeyDownHeld)) - { - mapIterator->state = KeyState::KeyDownHeld; - } - else if (press && stateIterator->state == KeyState::Idle) - { - mapIterator->state = KeyState::KeyDown; - } - else if (release && - (stateIterator->state == KeyState::KeyDown || stateIterator->state == KeyState::KeyDownHeld)) - { - mapIterator->state = KeyState::KeyUp; - } - else - { - mapIterator->state = KeyState::Idle; - } - } - } - } - - KeyState GlfwInputDevice::GetKeyState(const std::string& key) noexcept - { - size_t count = _mappedActions.size(); - for (size_t c = 0; c < count; c++) - { - if (_mappedActions[c].actionName == key) - { - return _mappedActions[c].state; - } - } - - _logger.logWarning("Requested action is not mapped: {}", key); - return KeyState::Idle; - } - - bool GlfwInputDevice::IsKeyPressed(const std::string& input) noexcept - { - for (auto action : _mappedActions) - { - if (action.actionName == input) - { - return action.state == KeyState::KeyDown; - } - } - - _logger.logWarning("Requested action is not mapped: {}", input); - return false; - } - - bool GlfwInputDevice::IsKeyHeld(const std::string& input) noexcept - { - for (auto action : _mappedActions) - { - if (action.actionName == input) - { - return action.state == KeyState::KeyDownHeld; - } - } - - _logger.logWarning("Requested action is not mapped: {}", input); - return false; - } - - bool GlfwInputDevice::IsKeyReleased(const std::string& input) noexcept - { - for (auto action : _mappedActions) - { - if (action.actionName == input) - { - return action.state == KeyState::KeyUp; - } - } - - _logger.logWarning("Requested action is not mapped: {}", input); - return false; - } - - InputAction& GlfwInputDevice::AddInputAction(const std::string& actionName, const std::string& keyIdentifier) - { - bool nameExists = false; - bool keyBoundAlready = false; - NovelKey pairedKey = GetAvailableKey(keyIdentifier); - - for (auto existingAction : _mappedActions) - { - if (existingAction.actionName == actionName) - { - nameExists = true; - } - if (existingAction.pairedKey == pairedKey) - { - keyBoundAlready = true; - } - } - - if (nameExists) - { - throw Exceptions::InvalidOperationException("Cannot add InputAction as the name has already been mapped!"); - } - else if (keyBoundAlready) - { - throw Exceptions::InvalidOperationException("Cannot add InputAction as key has already been bound!"); - } - else - { - _mappedActions.emplace_back(InputAction{actionName, pairedKey}); - return _mappedActions.back(); - } - } - - NovelKey& GlfwInputDevice::GetAvailableKey(const std::string& keyRequested) - { - if (keyRequested == "") - { - throw NovelRT::Exceptions::InvalidOperationException("Cannot request a key with no name."); - } - - for (auto key : _availableKeys) - { - if (key.first == keyRequested) - { - return _availableKeys.at(key.first); - } - } - - _logger.logError("Key {} not available from this input service.", keyRequested); - throw NovelRT::Exceptions::KeyNotFoundException("Unavailable input key requested from input service."); - } - - NovelRT::Utilities::Misc::Span GlfwInputDevice::GetAllMappings() noexcept - { - return _mappedActions; - } - - NovelRT::Maths::GeoVector2F GlfwInputDevice::GetMousePosition() noexcept - { - return _mousePos; } GlfwInputDevice::~GlfwInputDevice() diff --git a/src/NovelRT/Input/Glfw/GlfwInputPluginProvider.cpp b/input/Glfw/GlfwInputPluginProvider.cpp similarity index 88% rename from src/NovelRT/Input/Glfw/GlfwInputPluginProvider.cpp rename to input/Glfw/GlfwInputPluginProvider.cpp index df64ddbcc..8d0222b1b 100644 --- a/src/NovelRT/Input/Glfw/GlfwInputPluginProvider.cpp +++ b/input/Glfw/GlfwInputPluginProvider.cpp @@ -1,7 +1,7 @@ // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#include +#include namespace NovelRT::Input::Glfw { diff --git a/input/KeyStateFrameChangeLog.cpp b/input/KeyStateFrameChangeLog.cpp new file mode 100644 index 000000000..f0fb79b8d --- /dev/null +++ b/input/KeyStateFrameChangeLog.cpp @@ -0,0 +1,28 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. + +#include + +namespace NovelRT::Input +{ + void KeyStateFrameChangeLog::PushNewState(KeyState newState) noexcept + { + if (_currentState == newState) + { + return; + } + + _currentState = newState; + _changeCount++; + } + + KeyState KeyStateFrameChangeLog::GetCurrentState() const noexcept + { + return _currentState; + } + + uint32_t KeyStateFrameChangeLog::GetChangeCount() const noexcept + { + return _changeCount; + } +} diff --git a/src/NovelRT/Input/NovelKey.cpp b/input/NovelKey.cpp similarity index 90% rename from src/NovelRT/Input/NovelKey.cpp rename to input/NovelKey.cpp index a589bda74..16538ce8c 100644 --- a/src/NovelRT/Input/NovelKey.cpp +++ b/input/NovelKey.cpp @@ -1,8 +1,7 @@ // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#include -#include +#include namespace NovelRT::Input { diff --git a/include/NovelRT/Input/Glfw/GlfwInputDevice.h b/input/include/NovelRT/Input/Glfw/GlfwInputDevice.hpp similarity index 51% rename from include/NovelRT/Input/Glfw/GlfwInputDevice.h rename to input/include/NovelRT/Input/Glfw/GlfwInputDevice.hpp index 0d1f6ac28..62d928b3e 100644 --- a/include/NovelRT/Input/Glfw/GlfwInputDevice.h +++ b/input/include/NovelRT/Input/Glfw/GlfwInputDevice.hpp @@ -1,27 +1,42 @@ +#pragma once // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#ifndef NOVELRT_INPUT_GLFW_GLFWINPUTDEVICE_H -#define NOVELRT_INPUT_GLFW_GLFWINPUTDEVICE_H -#ifndef NOVELRT_INPUT_GLFW_H -#error NovelRT does not support including types explicitly by default. Please include Input.Glfw.h instead for the Input::Glfw namespace subset. -#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace NovelRT::Input::Glfw { class GlfwInputDevice final : public Input::IInputDevice { private: + uint32_t _previousBufferIndex; + uint32_t _currentBufferIndex; bool _isInitialised; GLFWwindow* _window; std::vector _previousStates; NovelRT::Maths::GeoVector2F _mousePos; + NovelRT::Maths::GeoVector2F _windowDimensions; + + void ProcessKeyInput(int32_t key, int32_t action); + void ProcessCursorMovement(NovelRT::Maths::GeoVector2F& pos); + void ProcessKeyState(int32_t action, KeyState state); + NovelRT::Maths::GeoVector2F DetermineMouseScreenPosition(NovelRT::Maths::GeoVector2F& pos); + void MapAllGlfwKeysToNovelKeys(); public: GlfwInputDevice() noexcept; - void Initialise(void* window) final; + void Initialise(std::shared_ptr device) final; void Update(Timing::Timestamp delta) final; [[nodiscard]] bool IsKeyPressed(const std::string& input) noexcept final; [[nodiscard]] bool IsKeyHeld(const std::string& input) noexcept final; @@ -32,9 +47,11 @@ namespace NovelRT::Input::Glfw [[nodiscard]] NovelKey& GetAvailableKey(const std::string& keyRequested) final; [[nodiscard]] NovelRT::Maths::GeoVector2F GetMousePosition() noexcept final; [[nodiscard]] NovelRT::Utilities::Misc::Span GetAllMappings() noexcept final; + [[nodiscard]] KeyStateFrameChangeLog GetCurrentChangeLog(const std::string& key) final; + [[nodiscard]] KeyStateFrameChangeLog GetPreviousChangeLog(const std::string& key) final; + [[nodiscard]] NovelRT::Utilities::Misc::Span> GetAllChangeLogs() final; ~GlfwInputDevice() final; }; } -#endif // NOVELRT_INPUT_GLFW_GLFWINPUTDEVICE_H diff --git a/include/NovelRT/Input/Glfw/GlfwInputPluginProvider.h b/input/include/NovelRT/Input/Glfw/GlfwInputPluginProvider.hpp similarity index 67% rename from include/NovelRT/Input/Glfw/GlfwInputPluginProvider.h rename to input/include/NovelRT/Input/Glfw/GlfwInputPluginProvider.hpp index af9a0184c..29b858fcf 100644 --- a/include/NovelRT/Input/Glfw/GlfwInputPluginProvider.h +++ b/input/include/NovelRT/Input/Glfw/GlfwInputPluginProvider.hpp @@ -1,12 +1,10 @@ +#pragma once // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#ifndef NOVELRT_INPUT_GLFW_GLFWINPUTPLUGINPROVIDER_H -#define NOVELRT_INPUT_GLFW_GLFWINPUTPLUGINPROVIDER_H -#ifndef NOVELRT_INPUT_GLFW_H -#error NovelRT does not support including types explicitly by default. Please include Input.Glfw.h instead for the Input::Glfw namespace subset. -#endif +#include +#include namespace NovelRT::Input::Glfw { @@ -27,4 +25,3 @@ namespace NovelRT::Input::Glfw }; } -#endif // NOVELRT_INPUT_GLFW_GLFWINPUTPLUGINPROVIDER_H diff --git a/include/NovelRT/Input/IInputDevice.h b/input/include/NovelRT/Input/IInputDevice.hpp similarity index 57% rename from include/NovelRT/Input/IInputDevice.h rename to input/include/NovelRT/Input/IInputDevice.hpp index fdf66f138..0d2b4c9c0 100644 --- a/include/NovelRT/Input/IInputDevice.h +++ b/input/include/NovelRT/Input/IInputDevice.hpp @@ -1,24 +1,33 @@ +#pragma once // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#ifndef NOVELRT_INPUT_IINPUTSERVICE_H -#define NOVELRT_INPUT_IINPUTSERVICE_H -#ifndef NOVELRT_INPUT_H -#error NovelRT does not support including types explicitly by default. Please include Input.h instead for the Input namespace subset. -#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace NovelRT::Input { class IInputDevice : public std::enable_shared_from_this { protected: + static inline const uint32_t DEFAULT_INPUT_BUFFER_COUNT = 6; LoggingService _logger; std::vector _mappedActions; std::map _availableKeys; + std::vector> _keyStates; + uint32_t _inputBufferCount; public: - virtual void Initialise(void* window) = 0; + virtual void Initialise(std::shared_ptr device) = 0; virtual void Update(Timing::Timestamp delta) = 0; [[nodiscard]] virtual bool IsKeyPressed(const std::string& key) = 0; [[nodiscard]] virtual bool IsKeyHeld(const std::string& key) = 0; @@ -29,9 +38,10 @@ namespace NovelRT::Input [[nodiscard]] virtual NovelKey& GetAvailableKey(const std::string& keyRequested) = 0; [[nodiscard]] virtual NovelRT::Maths::GeoVector2F GetMousePosition() = 0; [[nodiscard]] virtual NovelRT::Utilities::Misc::Span GetAllMappings() = 0; + [[nodiscard]] virtual KeyStateFrameChangeLog GetCurrentChangeLog(const std::string& key) = 0; + [[nodiscard]] virtual KeyStateFrameChangeLog GetPreviousChangeLog(const std::string& key) = 0; + [[nodiscard]] virtual NovelRT::Utilities::Misc::Span> GetAllChangeLogs() = 0; virtual ~IInputDevice() = default; }; } - -#endif // NOVELRT_INPUT_IINPUTSERVICE_H diff --git a/include/NovelRT/Input/InputAction.h b/input/include/NovelRT/Input/InputAction.hpp similarity index 53% rename from include/NovelRT/Input/InputAction.h rename to input/include/NovelRT/Input/InputAction.hpp index 511122052..bc981152b 100644 --- a/include/NovelRT/Input/InputAction.h +++ b/input/include/NovelRT/Input/InputAction.hpp @@ -1,12 +1,11 @@ +#pragma once // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#ifndef NOVELRT_INPUT_INPUTACTION_H -#define NOVELRT_INPUT_INPUTACTION_H -#ifndef NOVELRT_INPUT_H -#error NovelRT does not support including types explicitly by default. Please include Input.h instead for the Input namespace subset. -#endif +#include +#include +#include namespace NovelRT::Input { @@ -18,4 +17,3 @@ namespace NovelRT::Input }; } -#endif // NOVELRT_INPUT_INPUTACTION_H diff --git a/input/include/NovelRT/Input/KeyState.hpp b/input/include/NovelRT/Input/KeyState.hpp new file mode 100644 index 000000000..0fbc3ded0 --- /dev/null +++ b/input/include/NovelRT/Input/KeyState.hpp @@ -0,0 +1,21 @@ +#pragma once +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. + + +#include + +namespace NovelRT::Input +{ + /** + * @brief A set of key states. + */ + enum class KeyState : int32_t + { + Idle = -1, + KeyUp = 0, + KeyDown = 1, + KeyDownHeld = 2 + }; +} + diff --git a/input/include/NovelRT/Input/KeyStateFrameChangeLog.hpp b/input/include/NovelRT/Input/KeyStateFrameChangeLog.hpp new file mode 100644 index 000000000..60b32fa6a --- /dev/null +++ b/input/include/NovelRT/Input/KeyStateFrameChangeLog.hpp @@ -0,0 +1,24 @@ +#pragma once +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. + + +#include +#include + +namespace NovelRT::Input +{ + class KeyStateFrameChangeLog + { + + private: + KeyState _currentState = KeyState::Idle; + uint32_t _changeCount = 0; + + public: + void PushNewState(KeyState newState) noexcept; + KeyState GetCurrentState() const noexcept; + uint32_t GetChangeCount() const noexcept; + }; +} + diff --git a/include/NovelRT/Input/NovelKey.h b/input/include/NovelRT/Input/NovelKey.hpp similarity index 70% rename from include/NovelRT/Input/NovelKey.h rename to input/include/NovelRT/Input/NovelKey.hpp index 14d682498..68252e931 100644 --- a/include/NovelRT/Input/NovelKey.h +++ b/input/include/NovelRT/Input/NovelKey.hpp @@ -1,23 +1,19 @@ +#pragma once // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#ifndef NOVELRT_INPUT_NOVELKEY_H -#define NOVELRT_INPUT_NOVELKEY_H -#ifndef NOVELRT_INPUT_H -#error NovelRT does not support including types explicitly by default. Please include Input.h instead for the Input namespace subset. -#endif +#include +#include namespace NovelRT::Input { class NovelKey { - private: + public: int32_t _pairedKey; std::string _keyName; int32_t _modifier; - - public: NovelKey(std::string keyName = "", int32_t pairedKeyCode = -1, int32_t modifier = 0) noexcept; void PairKey(int32_t externalKeyCode) noexcept; void UnpairKey() noexcept; @@ -27,9 +23,8 @@ namespace NovelRT::Input [[nodiscard]] inline bool operator==(NovelKey& other) noexcept { - return (_keyName == other.GetKeyName()) || (_pairedKey == other.GetExternalKeyCode()); + return (_keyName == other.GetKeyName()) && (_pairedKey == other.GetExternalKeyCode()); }; }; } -#endif // NOVELRT_INPUT_NOVELKEY_H diff --git a/samples/InputEcsSample/main.cpp b/samples/InputEcsSample/main.cpp index 4db4deca6..1497cbd77 100644 --- a/samples/InputEcsSample/main.cpp +++ b/samples/InputEcsSample/main.cpp @@ -131,8 +131,7 @@ int main() scale += NovelRT::Maths::GeoVector2F{-0.2f, -0.2f}; } - if (events.TryGetComponent(mouseClick, input) && - (input.state == KeyState::KeyDown || input.state == KeyState::KeyDownHeld)) + if (events.TryGetComponent(mouseClick, input) && input.state == KeyState::KeyDown) { logger.logInfo("Clicked at {}, {}", input.mousePositionX, input.mousePositionY); } diff --git a/src/NovelRT.Interop/Input/NrtIInputDevice.cpp b/src/NovelRT.Interop/Input/NrtIInputDevice.cpp index 4d0c97d02..081d2beb9 100644 --- a/src/NovelRT.Interop/Input/NrtIInputDevice.cpp +++ b/src/NovelRT.Interop/Input/NrtIInputDevice.cpp @@ -4,7 +4,7 @@ #include "../LifetimeExtender.h" #include #include -#include +#include using namespace NovelRT::Input; using namespace NovelRT::Interop; diff --git a/src/NovelRT.Interop/Input/NrtInputAction.cpp b/src/NovelRT.Interop/Input/NrtInputAction.cpp index d6b9f86a8..b4e87e05a 100644 --- a/src/NovelRT.Interop/Input/NrtInputAction.cpp +++ b/src/NovelRT.Interop/Input/NrtInputAction.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include using namespace NovelRT::Input; diff --git a/src/NovelRT.Interop/Input/NrtNovelKey.cpp b/src/NovelRT.Interop/Input/NrtNovelKey.cpp index 102880abb..fae812db6 100644 --- a/src/NovelRT.Interop/Input/NrtNovelKey.cpp +++ b/src/NovelRT.Interop/Input/NrtNovelKey.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include using namespace NovelRT::Input; diff --git a/src/NovelRT/CMakeLists.txt b/src/NovelRT/CMakeLists.txt index 5b1fd6d73..bdeb384b9 100644 --- a/src/NovelRT/CMakeLists.txt +++ b/src/NovelRT/CMakeLists.txt @@ -19,10 +19,6 @@ set(CORE_SOURCES Ecs/Input/InputSystem.cpp Ecs/Narrative/NarrativePlayerSystem.cpp - Input/Glfw/GlfwInputDevice.cpp - Input/Glfw/GlfwInputPluginProvider.cpp - Input/NovelKey.cpp - Maths/GeoBounds.cpp Maths/QuadTree.cpp @@ -85,6 +81,7 @@ target_link_libraries(Engine ogg NovelRT-Graphics NovelRT-Audio + NovelRT-Input ) if(NOVELRT_INSTALL) diff --git a/src/NovelRT/Ecs/Input/InputSystem.cpp b/src/NovelRT/Ecs/Input/InputSystem.cpp index 0bb3f83d1..fad5cfe3e 100644 --- a/src/NovelRT/Ecs/Input/InputSystem.cpp +++ b/src/NovelRT/Ecs/Input/InputSystem.cpp @@ -7,10 +7,11 @@ namespace NovelRT::Ecs::Input { InputSystem::InputSystem(std::shared_ptr windowingProvider, std::shared_ptr inputProvider) + : _logger(Utilities::Misc::CONSOLE_LOG_ECS_INPUT) { _inputMap = std::map(); _device = inputProvider->GetInputService(); - _device->Initialise(windowingProvider->GetWindowingDevice()->GetHandle()); + _device->Initialise(windowingProvider->GetWindowingDevice()); } void InputSystem::Update(Timing::Timestamp delta, Ecs::Catalogue catalogue) @@ -18,7 +19,7 @@ namespace NovelRT::Ecs::Input _device->Update(delta); auto inputs = catalogue.GetComponentView(); - + auto mouse = _device->GetMousePosition(); for (auto&& input : _inputMap) { InputEventComponent in; @@ -32,7 +33,8 @@ namespace NovelRT::Ecs::Input case NovelRT::Input::KeyState::KeyDown: case NovelRT::Input::KeyState::KeyDownHeld: { - auto event = InputEventComponent{in.actionId, state, in.mousePositionX, in.mousePositionY}; + + auto event = InputEventComponent{in.actionId, state, mouse.x, mouse.y}; inputs.PushComponentUpdateInstruction(in.actionId, event); break; } @@ -48,7 +50,6 @@ namespace NovelRT::Ecs::Input { if (state != NovelRT::Input::KeyState::Idle) { - auto mouse = _device->GetMousePosition(); in = InputEventComponent{input.second, state, mouse.x, mouse.y}; inputs.AddComponent(input.second, in); } diff --git a/src/NovelRT/PluginManagement/TemporaryFnPtrs.cpp b/src/NovelRT/PluginManagement/TemporaryFnPtrs.cpp index 64802f1ad..b1e9b611a 100644 --- a/src/NovelRT/PluginManagement/TemporaryFnPtrs.cpp +++ b/src/NovelRT/PluginManagement/TemporaryFnPtrs.cpp @@ -2,7 +2,7 @@ // for more information. #include -#include +#include #include #include #include diff --git a/src/NovelRT/Windowing/Glfw/GlfwWindowingDevice.cpp b/src/NovelRT/Windowing/Glfw/GlfwWindowingDevice.cpp index 6f97d29aa..491532464 100644 --- a/src/NovelRT/Windowing/Glfw/GlfwWindowingDevice.cpp +++ b/src/NovelRT/Windowing/Glfw/GlfwWindowingDevice.cpp @@ -63,6 +63,21 @@ namespace NovelRT::Windowing::Glfw thisDevice->SizeChanged(Maths::GeoVector2F(static_cast(width), static_cast(height))); }); + glfwSetKeyCallback(window, [](auto window, auto key, auto /*scancode*/, auto action, auto /*mods*/) { + auto thisPtr = reinterpret_cast(glfwGetWindowUserPointer(window)); + thisPtr->KeyboardButtonChanged(ButtonChangeEventArgs{key, action}); + }); + + glfwSetMouseButtonCallback(window, [](auto window, auto mouseButton, auto action, auto /*mods*/) { + auto thisPtr = reinterpret_cast(glfwGetWindowUserPointer(window)); + thisPtr->MouseButtonClicked(ButtonChangeEventArgs{mouseButton, action}); + }); + + glfwSetCursorPosCallback(window, [](auto window, double x, double y) { + auto thisPtr = reinterpret_cast(glfwGetWindowUserPointer(window)); + thisPtr->CursorMoved(CursorPositionEventArgs{x, y}); + }); + _window = std::unique_ptr(window, glfwDestroyWindow); if (glfwVulkanSupported() == GLFW_FALSE)