diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 5a01a6186..7b6e738da 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -23,9 +23,18 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglew2.2 \ + sudo apt-get install -y build-essential libglew-dev libglew2.2 \ libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev \ libcurl4-openssl-dev libgtest-dev cmake squashfs-tools valgrind + # dependency for building sdl + # https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md + sudo apt-get install -y build-essential git make \ + pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ + libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ + libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \ + libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ + libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev \ + libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev # fix luajit paths sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a sudo ln -s /usr/include/luajit-2.1 /usr/include/lua @@ -35,10 +44,17 @@ jobs: cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=on .. sudo make install cd ../.. + # install SDL3 + git clone https://github.com/libsdl-org/SDL.git + cd SDL + cmake -G "Ninja Multi-Config" -B.build . + cmake --build .build --config Release -j 4 + sudo cmake --install .build --config Release + cd .. - name: Configure run: cmake -S . -B build -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_APPDIR=1 -DVOXELENGINE_BUILD_TESTS=ON - name: Build - run: cmake --build build -t install + run: cmake --build build -t install -j 4 - name: Run tests run: ctest --test-dir build - name: Run engine tests diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index ddaa95f8a..27d128226 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -39,7 +39,15 @@ jobs: # make && make install INSTALL_INC=/usr/include/lua run: | sudo apt-get update - sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libgtest-dev libcurl4-openssl-dev + sudo apt-get install libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libgtest-dev libcurl4-openssl-dev + # dependency for building sdl + # https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md + sudo apt-get install -y build-essential git make \ + pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ + libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ + libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \ + libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ + libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev # fix luajit paths sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua-5.1.a sudo ln -s /usr/include/luajit-2.1 /usr/include/lua @@ -49,6 +57,13 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=on .. sudo make install cd ../.. + # install SDL3 + git clone https://github.com/libsdl-org/SDL.git + cd SDL + cmake -G "Ninja Multi-Config" -B.build . + cmake --build .build --config Release -j 4 + sudo cmake --install .build --config Release + cd .. - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only @@ -61,7 +76,7 @@ jobs: - name: Build # Build your program with the given configuration run: | - cmake --build ${{github.workspace}}/build --config ${{inputs.build_type}} + cmake --build ${{github.workspace}}/build --config ${{inputs.build_type}} -j 4 mv ${{github.workspace}}/build/VoxelEngine VoxelEngine - name: Run tests if: ${{ inputs.run_tests }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f8fe3dfac..8cfc20084 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -17,13 +17,13 @@ jobs: - name: Install dependencies from brew run: | - brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm + brew install sdl3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm - name: Configure run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 - name: Build - run: cmake --build build -t install + run: cmake --build build -t install -j 4 - name: Make fix_dylibs.sh executable run: chmod +x dev/fix_dylibs.sh diff --git a/.github/workflows/windows-clang.yml b/.github/workflows/windows-clang.yml index cdf354dd6..3b860ed8c 100644 --- a/.github/workflows/windows-clang.yml +++ b/.github/workflows/windows-clang.yml @@ -48,7 +48,7 @@ jobs: mkdir build cd build cmake -G "MinGW Makefiles" -DVCPKG_TARGET_TRIPLET=x64-mingw-static -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake .. - cmake --build . --config Release + cmake --build . --config Release -j 4 - name: Package for Windows run: | mkdir packaged diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e5ee5a69b..03cdffcf4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -32,7 +32,7 @@ jobs: VCPKG_ROOT: ${{ github.workspace }}/vcpkg run: | cmake --preset default-vs-msvc-windows - cmake --build --preset default-vs-msvc-windows --config Release + cmake --build --preset default-vs-msvc-windows --config Release -j 4 - name: Run tests run: ctest --preset default-vs-msvc-windows - name: Run engine tests diff --git a/.gitignore b/.gitignore index c0b0e1c9d..92e754729 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ Debug/voxel_engine /export /config /out +/compile_commands.json /projects /misc diff --git a/Dockerfile b/Dockerfile index 9d0005f59..ad685f226 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ xauth \ gdb \ gdbserver \ - libglfw3-dev \ - libglfw3 \ + libsdl3-dev \ libglew-dev \ libglew2.2 \ libglm-dev \ diff --git a/README.md b/README.md index 30925fefd..c6e2a993e 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,19 @@ sudo make install ```sh su - -apt-get install entt-devel libglfw3-devel libGLEW-devel libglm-devel libpng-devel libvorbis-devel libopenal-devel libluajit-devel libstdc++13-devel-static libcurl-devel +apt-get install entt-devel SDL3-devel libGLEW-devel libglm-devel libpng-devel libvorbis-devel libopenal-devel libluajit-devel libstdc++13-devel-static libcurl-devel ``` #### Debian based distros ```sh -sudo apt install libglfw3 libglfw3-dev libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libcurl4-openssl-dev +sudo apt install libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libcurl4-openssl-dev ``` +> [!WARNING] +> You need manualy prebuild and install SDL3 lib. + + > [!TIP] > CMake missing `LUA_INCLUDE_DIR` and `LUA_LIBRARIES` fix: > @@ -48,21 +52,12 @@ sudo apt install libglfw3 libglfw3-dev libglew-dev libglm-dev libpng-dev libopen #### RHEL based distros ```sh -sudo dnf install glfw-devel glew-devel glm-devel libpng-devel libvorbis-devel openal-soft-devel luajit-devel libcurl-devel +sudo dnf install SDL3-devel glew-devel glm-devel libpng-devel libvorbis-devel openal-soft-devel luajit-devel libcurl-devel ``` #### Arch based distros - -If you use X11: - ```sh -sudo pacman -S glfw-x11 glew glm libpng libvorbis openal luajit libcurl -``` - -If you use Wayland: - -```sh -sudo pacman -S glfw-wayland glew glm libpng libvorbis openal luajit libcurl +sudo pacman -S sdl3 glew glm libpng libvorbis openal luajit libcurl ``` And install EnTT: @@ -92,7 +87,7 @@ cmake --build . --parallel ### Install libraries ```sh -brew install glfw3 glew glm libpng libvorbis lua luajit libcurl openal-soft skypjack/entt/entt +brew install sdl3 glew glm libpng libvorbis lua luajit libcurl openal-soft skypjack/entt/entt ``` > [!TIP] diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index 16fcf57a7..19b818f67 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -22,7 +22,6 @@ AppDir: - libexpat1 - libfam0 - libgcrypt20 - - libglfw3 - libglew2.2 - libpng16-16 - libopenal1 diff --git a/flake.lock b/flake.lock index 55f50fc42..ff2363b8d 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1722802969, - "narHash": "sha256-bPhyAXNnVerBZusxOuPMhMm0X7hSFLFKcH+7ynfgLjs=", + "lastModified": 1760349414, + "narHash": "sha256-W4Ri1ZwYuNcBzqQQa7NnWfrv0wHMo7rduTWjIeU9dZk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "785feb91183a50959823ff9ba9ef673105259cd5", + "rev": "c12c63cd6c5eb34c7b4c3076c6a99e00fcab86ec", "type": "github" }, "original": { @@ -59,3 +59,4 @@ "root": "root", "version": 7 } + diff --git a/flake.nix b/flake.nix index bc9ead2b0..1c3daeb04 100644 --- a/flake.nix +++ b/flake.nix @@ -28,7 +28,7 @@ buildInputs = with pkgs; [ glm - glfw + sdl3 glew zlib libpng @@ -42,7 +42,7 @@ ]; # libglvnd packages = with pkgs; [ - glfw + sdl3 mesa freeglut entt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 000f2c3c0..ff4b2ca9a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,7 @@ project(VoxelEngineSrc) -file(GLOB_RECURSE headers ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp) -file(GLOB_RECURSE sources ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +file(GLOB_RECURSE headers CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp) +file(GLOB_RECURSE sources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) list(REMOVE_ITEM sources ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) add_library(VoxelEngineSrc STATIC ${sources} ${headers}) @@ -18,7 +18,7 @@ endif() find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) find_package(CURL REQUIRED) -find_package(glfw3 REQUIRED) +find_package(SDL3 REQUIRED) if(NOT APPLE) find_package(EnTT REQUIRED) endif() @@ -65,7 +65,7 @@ target_include_directories(VoxelEngineSrc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries( VoxelEngineSrc - PRIVATE glfw + PRIVATE SDL3::SDL3 OpenGL::GL GLEW::GLEW ZLIB::ZLIB diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index a977f354b..71bcd580f 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -308,6 +308,7 @@ void Engine::nextFrame() { : settings.display.framerate.get() ); window->swapBuffers(); + // todo: process it on another thread input->pollEvents(); } diff --git a/src/graphics/core/Shader.cpp b/src/graphics/core/Shader.cpp index 8cc6f2025..901fd6929 100644 --- a/src/graphics/core/Shader.cpp +++ b/src/graphics/core/Shader.cpp @@ -1,22 +1,13 @@ #include "Shader.hpp" -#include -#include -#include -#include - #include - #include -#include #include "coders/GLSLExtension.hpp" #include "debug/Logger.hpp" static debug::Logger logger("gl-shader"); -namespace fs = std::filesystem; - GLSLExtension* Shader::preprocessor = new GLSLExtension(); Shader* Shader::used = nullptr; diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 3ab438b1f..c58cded60 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -166,7 +166,7 @@ void GUI::actMouse(float delta, const CursorState& cursor) { } if (hover) { - for (Mousecode code : MOUSECODES_ALL) { + for (Mousecode code : mousecodes_all) { if (input.jclicked(code)) { hover->clicked(code); } diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index a09a47fa9..65ba7dee8 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -617,8 +617,15 @@ bool TextBox::isAutoResize() const { return autoresize; } +void TextBox::defocus() { + Container::defocus(); + gui.getInput().stopTextInput(); +} + + void TextBox::onFocus() { Container::onFocus(); + gui.getInput().startTextInput(); if (onEditStart) { setCaret(input.size()); onEditStart(); diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index d5b640ac8..f5adc2bd1 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -230,6 +230,7 @@ namespace gui { virtual bool isShowLineNumbers() const; virtual void reposition() override; + virtual void defocus() override; virtual void onFocus() override; virtual void refresh() override; virtual void doubleClick(int x, int y) override; diff --git a/src/graphics/ui/elements/UINode.hpp b/src/graphics/ui/elements/UINode.hpp index fc64527fd..f1536094e 100644 --- a/src/graphics/ui/elements/UINode.hpp +++ b/src/graphics/ui/elements/UINode.hpp @@ -192,6 +192,7 @@ namespace gui { virtual UINode* listenFocus(const onaction& action); virtual UINode* listenDefocus(const onaction& action); + virtual void defocus(); virtual void onFocus(); virtual void doubleClick(int x, int y); virtual void click(int x, int y); @@ -201,7 +202,6 @@ namespace gui { virtual void scrolled(int value); bool isPressed() const; - void defocus(); bool isFocused() const; /// @brief Check if element catches all user input when focused diff --git a/src/window/Window.hpp b/src/window/Window.hpp index 135e4f7d9..f56b97f52 100644 --- a/src/window/Window.hpp +++ b/src/window/Window.hpp @@ -4,24 +4,17 @@ #include #include "graphics/core/commons.hpp" -#include "typedefs.hpp" class ImageData; class Input; struct DisplaySettings; -enum class WindowMode { - WINDOWED, - FULLSCREEN, - BORDERLESS -}; +enum class WindowMode { WINDOWED, FULLSCREEN, BORDERLESS }; class Window { public: - Window(glm::ivec2 size) : size(std::move(size)) {} - virtual ~Window() = default; - virtual void swapBuffers() = 0; + virtual void swapBuffers() noexcept = 0; virtual bool isMaximized() const = 0; virtual bool isFocused() const = 0; @@ -47,14 +40,17 @@ public: // todo: move somewhere virtual std::unique_ptr takeScreenshot() = 0; - const glm::ivec2& getSize() const { + [[nodiscard]] virtual bool isValid() const = 0; + + virtual const glm::ivec2& getSize() const { return size; } + virtual void setSize(const glm::ivec2& s) { + size = std::move(s); + } - static std::tuple< - std::unique_ptr, - std::unique_ptr - > initialize(DisplaySettings* settings, std::string title); + static std::tuple, std::unique_ptr> + initialize(DisplaySettings* settings, std::string title); protected: glm::ivec2 size; WindowMode mode = WindowMode::WINDOWED; diff --git a/src/window/detail/GLFWWindow.cpp b/src/window/detail/GLFWWindow.cpp deleted file mode 100644 index 27f56d3ce..000000000 --- a/src/window/detail/GLFWWindow.cpp +++ /dev/null @@ -1,752 +0,0 @@ -#include "window/Window.hpp" - -#include -#include -#include - -#include -#include -#include - -#include "debug/Logger.hpp" -#include "graphics/core/ImageData.hpp" -#include "graphics/core/Texture.hpp" -#include "settings.hpp" -#include "util/ObjectsKeeper.hpp" -#include "util/platform.hpp" -#include "window/input.hpp" - -static debug::Logger logger("window"); - -static std::unordered_set supported_gl_extensions; -static void window_size_callback(GLFWwindow* window, int width, int height); - -static void init_gl_extensions_list() { - GLint numExtensions = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions); - - for (GLint i = 0; i < numExtensions; ++i) { - const char *ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, i)); - if (ext) { - supported_gl_extensions.insert(ext); - } - } -} - -static bool is_gl_extension_supported(const char *extension) { - if (!extension || !*extension) { - return false; - } - return supported_gl_extensions.find(extension) != supported_gl_extensions.end(); -} - -static const char* gl_error_name(int error) { - switch (error) { - case GL_DEBUG_TYPE_ERROR: return "ERROR"; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; - case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; - case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; - case GL_DEBUG_TYPE_OTHER: return "OTHER"; - } - return "UNKNOWN"; -} - -static const char* gl_severity_name(int severity) { - switch (severity) { - case GL_DEBUG_SEVERITY_LOW: return "LOW"; - case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; - case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; - case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; - } - return "UNKNOWN"; -} - -static void GLAPIENTRY gl_message_callback( - GLenum source, - GLenum type, - GLuint id, - GLenum severity, - GLsizei length, - const GLchar* message, - const void* userParam -) { - if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { - return; - } - if (!ENGINE_DEBUG_BUILD && severity != GL_DEBUG_SEVERITY_HIGH) { - return; - } - logger.warning() << "GL:" << gl_error_name(type) << ":" - << gl_severity_name(severity) << ": " << message; -} - -static bool initialize_gl(int width, int height) { - glewExperimental = GL_TRUE; - - GLenum glewErr = glewInit(); - if (glewErr != GLEW_OK) { - if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY) { - // see issue #240 - logger.warning() - << "glewInit() returned GLEW_ERROR_NO_GLX_DISPLAY; ignored"; - } else { - logger.error() << "failed to initialize GLEW:\n" - << glewGetErrorString(glewErr); - return true; - } - } - -#ifndef __APPLE__ - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(gl_message_callback, nullptr); -#endif - - glViewport(0, 0, width, height); - glClearColor(0.0f, 0.0f, 0.0f, 1); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - GLint maxTextureSize[1] {static_cast(Texture::MAX_RESOLUTION)}; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxTextureSize); - if (maxTextureSize[0] > 0) { - Texture::MAX_RESOLUTION = maxTextureSize[0]; - logger.info() << "max texture size is " << Texture::MAX_RESOLUTION; - } - - const GLubyte* vendor = glGetString(GL_VENDOR); - const GLubyte* renderer = glGetString(GL_RENDERER); - logger.info() << "GL Vendor: " << reinterpret_cast(vendor); - logger.info() << "GL Renderer: " << reinterpret_cast(renderer); - logger.info() << "GLFW: " << glfwGetVersionString(); - return false; -} - -static const char* glfw_error_name(int error) { - switch (error) { - case GLFW_NO_ERROR: - return "no error"; - case GLFW_NOT_INITIALIZED: - return "not initialized"; - case GLFW_NO_CURRENT_CONTEXT: - return "no current context"; - case GLFW_INVALID_ENUM: - return "invalid enum"; - case GLFW_INVALID_VALUE: - return "invalid value"; - case GLFW_OUT_OF_MEMORY: - return "out of memory"; - case GLFW_API_UNAVAILABLE: - return "api unavailable"; - case GLFW_VERSION_UNAVAILABLE: - return "version unavailable"; - case GLFW_PLATFORM_ERROR: - return "platform error"; - case GLFW_FORMAT_UNAVAILABLE: - return "format unavailable"; - case GLFW_NO_WINDOW_CONTEXT: - return "no window context"; - default: - return "unknown error"; - } -} - -static void glfw_error_callback(int error, const char* description) { - auto logline = logger.error(); - logline << "GLFW error [0x" << std::hex << error << " - " - << glfw_error_name(error) << "]"; - if (description) { - logline << ": " << description; - } -} - -inline constexpr short KEYS_BUFFER_SIZE = 1036; -inline constexpr short MOUSE_KEYS_OFFSET = 1024; - -static GLFWcursor* standard_cursors[static_cast(CursorShape::LAST) + 1] = {}; - -class GLFWInput : public Input { -public: - int scroll = 0; - uint currentFrame = 0; - uint frames[KEYS_BUFFER_SIZE] {}; - std::vector codepoints; - std::vector pressedKeys; - Bindings bindings; - bool keys[KEYS_BUFFER_SIZE] {}; - std::unordered_map> keyCallbacks; - - GLFWInput(GLFWwindow* window) - : window(window) { - } - - void pollEvents() override { - delta.x = 0.0f; - delta.y = 0.0f; - scroll = 0; - currentFrame++; - codepoints.clear(); - pressedKeys.clear(); - glfwPollEvents(); - - for (auto& [_, binding] : bindings.getAll()) { - if (!binding.enabled) { - binding.state = false; - continue; - } - binding.justChanged = false; - - bool newstate = false; - switch (binding.type) { - case InputType::KEYBOARD: - newstate = pressed(static_cast(binding.code)); - break; - case InputType::MOUSE: - newstate = clicked(static_cast(binding.code)); - break; - } - - if (newstate) { - if (!binding.state) { - binding.state = true; - binding.justChanged = true; - binding.onactived.notify(); - } - } else { - if (binding.state) { - binding.state = false; - binding.justChanged = true; - } - } - } - } - - void onKeyCallback(int key, bool pressed) { - bool prevPressed = keys[key]; - keys[key] = pressed; - frames[key] = currentFrame; - if (pressed && !prevPressed) { - const auto& callbacks = keyCallbacks.find(static_cast(key)); - if (callbacks != keyCallbacks.end()) { - callbacks->second.notify(); - } - } - if (pressed && key < MOUSE_KEYS_OFFSET) { - pressedKeys.push_back(static_cast(key)); - } - } - - void onMouseCallback(int button, bool pressed) { - int key = button + MOUSE_KEYS_OFFSET; - onKeyCallback(key, pressed); - } - - const char* getClipboardText() const override { - return glfwGetClipboardString(window); - } - - void setClipboardText(const char* text) override { - glfwSetClipboardString(window, text); - } - - int getScroll() override { - return scroll; - } - - bool pressed(Keycode key) const override { - int keycode = static_cast(key); - if (keycode < 0 || keycode >= KEYS_BUFFER_SIZE) { - return false; - } - return keys[keycode]; - } - bool jpressed(Keycode keycode) const override { - return pressed(keycode) && - frames[static_cast(keycode)] == currentFrame; - } - - bool clicked(Mousecode code) const override { - return pressed( - static_cast(MOUSE_KEYS_OFFSET + static_cast(code)) - ); - } - bool jclicked(Mousecode code) const override { - return clicked(code) && - frames[static_cast(code) + MOUSE_KEYS_OFFSET] == - currentFrame; - } - - CursorState getCursor() const override { - return {isCursorLocked(), cursor, delta}; - } - - bool isCursorLocked() const override { - return cursorLocked; - } - - void toggleCursor() override { - cursorDrag = false; - if (cursorLocked) { - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - } else { - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - } - cursorLocked = !cursorLocked; - } - - void setCursorPosition(double xpos, double ypos) { - if (cursorDrag) { - delta.x += xpos - cursor.x; - delta.y += ypos - cursor.y; - } else { - cursorDrag = true; - } - cursor.x = xpos; - cursor.y = ypos; - } - - Bindings& getBindings() override { - return bindings; - } - - const Bindings& getBindings() const override { - return bindings; - } - - ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) override { - return keyCallbacks[key].add(std::move(callback)); - } - - const std::vector& getPressedKeys() const override { - return pressedKeys; - } - - const std::vector& getCodepoints() const override { - return codepoints; - } -private: - GLFWwindow* window; - bool cursorLocked = false; - bool cursorDrag = false; - glm::vec2 delta; - glm::vec2 cursor; -}; -static_assert(!std::is_abstract()); - -class GLFWWindow : public Window { -public: - GLFWInput& input; - DisplaySettings* settings; - - GLFWWindow( - GLFWInput& glfwInput, - GLFWwindow* window, - DisplaySettings* settings, - int width, - int height - ) - : Window({width, height}), - input(glfwInput), - settings(settings), - window(window) { - } - - ~GLFWWindow() { - for (int i = 0; i <= static_cast(CursorShape::LAST); i++) { - glfwDestroyCursor(standard_cursors[i]); - } - glfwTerminate(); - } - - double time() override { - return glfwGetTime(); - } - - void swapBuffers() override { - glfwSwapBuffers(window); - resetScissor(); - if (framerate > 0) { - auto elapsedTime = time() - prevSwap; - auto frameTime = 1.0 / framerate; - if (elapsedTime < frameTime) { - platform::sleep( - static_cast((frameTime - elapsedTime) * 1000) - ); - } - } - prevSwap = time(); - } - - bool isMaximized() const override { - return glfwGetWindowAttrib(window, GLFW_MAXIMIZED); - } - - bool isFocused() const override { - return glfwGetWindowAttrib(window, GLFW_FOCUSED); - } - - bool isIconified() const override { - return glfwGetWindowAttrib(window, GLFW_ICONIFIED); - } - - bool isShouldClose() const override { - return glfwWindowShouldClose(window); - } - - void setShouldClose(bool flag) override { - glfwSetWindowShouldClose(window, flag); - } - - void setCursor(CursorShape shape) override { - if (cursor == shape) { - return; - } - cursor = shape; - // nullptr cursor is valid for GLFW - glfwSetCursor(window, standard_cursors[static_cast(shape)]); - } - - void setMode(WindowMode mode) override { - Window::mode = mode; - GLFWmonitor* monitor = glfwGetPrimaryMonitor(); - const GLFWvidmode* glfwMode = glfwGetVideoMode(monitor); - - if (input.isCursorLocked()){ - input.toggleCursor(); - } - - if (mode == WindowMode::FULLSCREEN) { - const int width = glfwMode->width; - const int height = glfwMode->height; - const int refreshRate = glfwMode->refreshRate; - glfwGetWindowPos(window, &posX, &posY); - glfwSetWindowMonitor(window, monitor, 0, 0, width, height, refreshRate); - } - else if(mode == WindowMode::BORDERLESS) { - glfwGetWindowPos(window, &posX, &posY); - glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_FALSE); - glfwSetWindowAttrib(window, GLFW_RESIZABLE, GLFW_FALSE); - glfwSetWindowSize(window, glfwMode->width, glfwMode->height); - glfwSetWindowPos(window, 0, 0); - } else { - glfwSetWindowMonitor( - window, - nullptr, - posX, - posY, - settings->width.get(), - settings->height.get(), - GLFW_DONT_CARE - ); - glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_TRUE); - glfwSetWindowAttrib(window, GLFW_RESIZABLE, GLFW_TRUE); - window_size_callback(window, settings->width.get(), settings->height.get()); - } - - double xPos, yPos; - glfwGetCursorPos(window, &xPos, &yPos); - input.setCursorPosition(xPos, yPos); - } - - WindowMode getMode() const override { - return mode; - } - - void setIcon(const ImageData* image) override { - if (image == nullptr) { - glfwSetWindowIcon(window, 0, nullptr); - return; - } - GLFWimage icon { - static_cast(image->getWidth()), - static_cast(image->getHeight()), - image->getData()}; - glfwSetWindowIcon(window, 1, &icon); - } - - void setSize(int width, int height) { - glViewport(0, 0, width, height); - size = {width, height}; - - if (mode == WindowMode::WINDOWED && !isMaximized()) { - settings->width.set(width); - settings->height.set(height); - } - } - - void pushScissor(glm::vec4 area) override { - if (scissorStack.empty()) { - glEnable(GL_SCISSOR_TEST); - } - scissorStack.push(scissorArea); - - area.z += glm::ceil(area.x); - area.w += glm::ceil(area.y); - - area.x = glm::max(area.x, scissorArea.x); - area.y = glm::max(area.y, scissorArea.y); - - area.z = glm::min(area.z, scissorArea.z); - area.w = glm::min(area.w, scissorArea.w); - - if (area.z < 0.0f || area.w < 0.0f) { - glScissor(0, 0, 0, 0); - } else { - glScissor( - area.x, - size.y - area.w, - std::max(0, static_cast(glm::ceil(area.z - area.x))), - std::max(0, static_cast(glm::ceil(area.w - area.y))) - ); - } - scissorArea = area; - } - - void resetScissor() override { - scissorArea = glm::vec4(0.0f, 0.0f, size.x, size.y); - scissorStack = std::stack(); - glDisable(GL_SCISSOR_TEST); - } - - void popScissor() override { - if (scissorStack.empty()) { - logger.warning() << "extra Window::popScissor call"; - return; - } - glm::vec4 area = scissorStack.top(); - scissorStack.pop(); - if (area.z < 0.0f || area.w < 0.0f) { - glScissor(0, 0, 0, 0); - } else { - glScissor( - area.x, - size.y - area.w, - std::max(0, static_cast(area.z - area.x)), - std::max(0, static_cast(area.w - area.y)) - ); - } - if (scissorStack.empty()) { - glDisable(GL_SCISSOR_TEST); - } - scissorArea = area; - } - - std::unique_ptr takeScreenshot() override { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - auto data = std::make_unique(size.x * size.y * 3); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0, 0, size.x, size.y, GL_RGB, GL_UNSIGNED_BYTE, data.get()); - return std::make_unique( - ImageFormat::rgb888, size.x, size.y, data.release() - ); - } - - void setFramerate(int framerate) override { - if ((framerate != -1) != (this->framerate != -1)) { - glfwSwapInterval(framerate == -1); - } - this->framerate = framerate; - } - -private: - GLFWwindow* window; - CursorShape cursor = CursorShape::ARROW; - int framerate = -1; - std::stack scissorStack; - glm::vec4 scissorArea; - double prevSwap = 0.0; - int posX = 0; - int posY = 0; -}; -static_assert(!std::is_abstract()); - -static void mouse_button_callback(GLFWwindow* window, int button, int action, int) { - auto handler = static_cast(glfwGetWindowUserPointer(window)); - handler->input.onMouseCallback(button, action == GLFW_PRESS); -} - -static void character_callback(GLFWwindow* window, unsigned int codepoint) { - auto handler = static_cast(glfwGetWindowUserPointer(window)); - handler->input.codepoints.push_back(codepoint); -} - -static void key_callback( - GLFWwindow* window, int key, int /*scancode*/, int action, int /*mode*/ -) { - auto handler = static_cast(glfwGetWindowUserPointer(window)); - auto& input = handler->input; - if (key == GLFW_KEY_UNKNOWN) { - return; - } - if (action == GLFW_PRESS) { - input.onKeyCallback(key, true); - - } else if (action == GLFW_RELEASE) { - input.onKeyCallback(key, false); - } else if (action == GLFW_REPEAT) { - input.onKeyCallback(key, true); - } -} - -static void window_size_callback(GLFWwindow* window, int width, int height) { - auto handler = static_cast(glfwGetWindowUserPointer(window)); - if (width && height) { - handler->setSize(width, height); - } - handler->resetScissor(); -} - -static void scroll_callback(GLFWwindow* window, double, double yoffset) { - auto handler = static_cast(glfwGetWindowUserPointer(window)); - handler->input.scroll += yoffset; -} - -static void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) { - auto handler = static_cast(glfwGetWindowUserPointer(window)); - handler->input.setCursorPosition(xpos, ypos); -} - -static void iconify_callback(GLFWwindow* window, int iconified) { - auto handler = static_cast(glfwGetWindowUserPointer(window)); - if (handler->getMode() == WindowMode::FULLSCREEN && iconified == 0) { - GLFWmonitor* monitor = glfwGetPrimaryMonitor(); - const GLFWvidmode* mode = glfwGetVideoMode(monitor); - glfwSetWindowMonitor( - window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate - ); - } -} - -static void create_standard_cursors() { - for (int i = 0; i <= static_cast(CursorShape::LAST); i++) { - int cursor = GLFW_ARROW_CURSOR + i; - // GLFW 3.3 does not support some cursors - if (GLFW_VERSION_MAJOR <= 3 && GLFW_VERSION_MINOR <= 3 && - cursor > GLFW_VRESIZE_CURSOR) { - break; - } - standard_cursors[i] = glfwCreateStandardCursor(cursor); - } -} - -static void setup_callbacks(GLFWwindow* window) { - glfwSetKeyCallback(window, key_callback); - glfwSetMouseButtonCallback(window, mouse_button_callback); - glfwSetCursorPosCallback(window, cursor_pos_callback); - glfwSetWindowSizeCallback(window, window_size_callback); - glfwSetCharCallback(window, character_callback); - glfwSetScrollCallback(window, scroll_callback); - glfwSetWindowIconifyCallback(window, iconify_callback); -} - -std::tuple< - std::unique_ptr, - std::unique_ptr -> Window::initialize(DisplaySettings* settings, std::string title) { - int width = settings->width.get(); - int height = settings->height.get(); - - glfwSetErrorCallback(glfw_error_callback); - if (glfwInit() == GLFW_FALSE) { - logger.error() << "failed to initialize GLFW"; - return {nullptr, nullptr}; - } - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); -#if GLFW_VERSION_MAJOR >= 3 && GLFW_VERSION_MINOR >= 4 - // see issue #465 - glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GL_FALSE); -#endif -#ifdef __APPLE__ - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); -#else - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); -#endif - glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); - glfwWindowHint(GLFW_SAMPLES, settings->samples.get()); - - auto window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); - if (window == nullptr) { - logger.error() << "failed to create GLFW window"; - glfwTerminate(); - return {nullptr, nullptr}; - } - glfwMakeContextCurrent(window); - - glewExperimental = GL_TRUE; - - GLenum glewErr = glewInit(); - if (glewErr != GLEW_OK) { - if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY) { - // see issue #240 - logger.warning() - << "glewInit() returned GLEW_ERROR_NO_GLX_DISPLAY; ignored"; - } else { - logger.error() << "failed to initialize GLEW:\n" - << glewGetErrorString(glewErr); - glfwTerminate(); - return {nullptr, nullptr}; - } - } - - init_gl_extensions_list(); - - #ifndef __APPLE__ - if (is_gl_extension_supported("GL_KHR_debug")) { - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(gl_message_callback, nullptr); - } - #endif - - glViewport(0, 0, width, height); - glClearColor(0.0f, 0.0f, 0.0f, 1); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - GLint maxTextureSize[1] {static_cast(Texture::MAX_RESOLUTION)}; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxTextureSize); - if (maxTextureSize[0] > 0) { - Texture::MAX_RESOLUTION = maxTextureSize[0]; - logger.info() << "max texture size is " << Texture::MAX_RESOLUTION; - } - setup_callbacks(window); - - glfwSwapInterval(1); - input_util::initialize(); - create_standard_cursors(); - - glm::vec2 scale; - glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &scale.x, &scale.y); - logger.info() << "monitor content scale: " << scale.x << "x" << scale.y; - - if (initialize_gl(width, height)) { - glfwTerminate(); - return {nullptr, nullptr}; - } - - auto inputPtr = std::make_unique(window); - auto windowPtr = std::make_unique( - *inputPtr, window, settings, width, height - ); - glfwSetWindowUserPointer(window, windowPtr.get()); - return {std::move(windowPtr), std::move(inputPtr)}; -} - -void display::clear() { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -void display::clearDepth() { - glClear(GL_DEPTH_BUFFER_BIT); -} - -void display::setBgColor(glm::vec3 color) { - glClearColor(color.r, color.g, color.b, 1.0f); -} - -void display::setBgColor(glm::vec4 color) { - glClearColor(color.r, color.g, color.b, color.a); -} diff --git a/src/window/detail/SDLInput.cpp b/src/window/detail/SDLInput.cpp new file mode 100644 index 000000000..38a25fae7 --- /dev/null +++ b/src/window/detail/SDLInput.cpp @@ -0,0 +1,300 @@ +#include "window/detail/SDLInput.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "debug/Logger.hpp" +#include "util/stringutil.hpp" +#include "window/detail/SDLWindow.hpp" +#include "window/input.hpp" + +static debug::Logger logger("input"); + +static std::unordered_map keycodes { + {"enter", SDL_SCANCODE_RETURN}, + {"space", SDL_SCANCODE_SPACE}, + {"backspace", SDL_SCANCODE_BACKSPACE}, + {"caps-lock", SDL_SCANCODE_CAPSLOCK}, + {"escape", SDL_SCANCODE_ESCAPE}, + {"delete", SDL_SCANCODE_DELETE}, + {"home", SDL_SCANCODE_HOME}, + {"end", SDL_SCANCODE_END}, + {"tab", SDL_SCANCODE_TAB}, + {"insert", SDL_SCANCODE_INSERT}, + {"page-down", SDL_SCANCODE_PAGEDOWN}, + {"page-up", SDL_SCANCODE_PAGEUP}, + {"left-shift", SDL_SCANCODE_LSHIFT}, + {"right-shift", SDL_SCANCODE_RSHIFT}, + {"left-ctrl", SDL_SCANCODE_LCTRL}, + {"right-ctrl", SDL_SCANCODE_RCTRL}, + {"left-alt", SDL_SCANCODE_LALT}, + {"right-alt", SDL_SCANCODE_RALT}, + {"left-super", SDL_SCANCODE_LGUI}, + {"right-super", SDL_SCANCODE_RGUI}, + {"grave-accent", SDL_SCANCODE_GRAVE}, + {"left", SDL_SCANCODE_LEFT}, + {"right", SDL_SCANCODE_RIGHT}, + {"down", SDL_SCANCODE_DOWN}, + {"up", SDL_SCANCODE_UP}, +}; +static std::unordered_map mousecodes { + {"left", SDL_BUTTON_LEFT}, + {"right", SDL_BUTTON_RIGHT}, + {"middle", SDL_BUTTON_MIDDLE}, + {"side1", SDL_BUTTON_X1}, + {"side2", SDL_BUTTON_X2}, +}; + +static std::unordered_map keynames {}; +static std::unordered_map buttonsnames {}; + +std::string input_util::get_name(Mousecode code) { + const auto found = buttonsnames.find(static_cast(code)); + if (found == buttonsnames.end()) { + return "unknown"; + } + return found->second; +} + +std::string input_util::get_name(Keycode code) { + const auto found = keynames.find(static_cast(code)); + if (found == keynames.end()) { + return "unknown"; + } + return found->second; +} + +void input_util::initialize() { + keycodes[std::to_string(0)] = SDL_SCANCODE_0; + for (int i = 1; i <= 9; i++) { + keycodes[std::to_string(i)] = SDL_SCANCODE_1 + i; + } + for (int i = 0; i < 25; i++) { + keycodes["f" + std::to_string(i + 1)] = SDL_SCANCODE_F1 + i; + } + for (char i = 'a'; i <= 'z'; i++) { + keycodes[std::string({i})] = SDL_SCANCODE_A - 'a' + i; + } + for (const auto& entry : keycodes) { + keynames[entry.second] = entry.first; + } + for (const auto& entry : mousecodes) { + buttonsnames[entry.second] = entry.first; + } +} + +std::string input_util::to_string(Keycode code) { + auto icode_repr = static_cast(code); + const char* name = SDL_GetKeyName( + SDL_GetKeyFromScancode(icode_repr, SDL_KMOD_NONE, false) + ); + return std::string(name); +} + +Keycode input_util::keycode_from(const std::string& name) { + auto found = std::find_if( + std::begin(keynames), std::end(keynames), [&name](auto&& p) { + return p.second == name; + } + ); + // Compatibility with old names + if (found != keynames.end()) return static_cast(found->first); + + return static_cast(SDL_GetScancodeFromName(name.c_str())); +} + +Mousecode input_util::mousecode_from(const std::string& name) { + const auto& found = mousecodes.find(name); + if (found == mousecodes.end()) { + return Mousecode::UNKNOWN; + } + return static_cast(found->second); +} + +SDLInput::SDLInput(SDLWindow& window) : window(window) { + input_util::initialize(); +} + +void SDLInput::pollEvents() { + delta.x = 0.0f; + delta.y = 0.0f; + scroll = 0; + currentFrame++; + codepoints.clear(); + pressedKeys.clear(); + + std::string text {}; + uint size {}; + + bool prevPressed = false; + + static SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_EVENT_QUIT: + window.setShouldClose(true); + break; + case SDL_EVENT_KEY_DOWN: + prevPressed = keys[event.key.scancode]; + keys[event.key.scancode] = true; + frames[event.key.scancode] = currentFrame; + if (!prevPressed) { + keyCallbacks[static_cast(event.key.scancode)] + .notify(); + } + pressedKeys.push_back(static_cast(event.key.scancode)); + break; + case SDL_EVENT_KEY_UP: + keys[event.key.scancode] = false; + frames[event.key.scancode] = currentFrame; + break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + prevPressed = keys[event.button.button + mouse_keys_offset]; + keys[event.button.button + mouse_keys_offset] = true; + frames[event.button.button + mouse_keys_offset] = currentFrame; + if (!prevPressed) { + keyCallbacks[static_cast( + event.button.button + mouse_keys_offset + )] + .notify(); + } + break; + case SDL_EVENT_MOUSE_BUTTON_UP: + keys[event.button.button + mouse_keys_offset] = false; + frames[event.button.button + mouse_keys_offset] = currentFrame; + break; + case SDL_EVENT_MOUSE_MOTION: + if (cursorDrag) { + delta += glm::vec2 {event.motion.xrel, event.motion.yrel}; + } else { + cursorDrag = true; + } + cursor = {event.motion.x, event.motion.y}; + break; + case SDL_EVENT_MOUSE_WHEEL: + scroll += event.wheel.integer_y; + break; + case SDL_EVENT_WINDOW_RESIZED: + window.setSize({event.window.data1, event.window.data2}); + break; + case SDL_EVENT_TEXT_INPUT: + text += event.text.text; + } + } + if (!text.empty()) { + codepoints.push_back(util::decode_utf8(size, text.c_str())); + } + + for (auto& [_, binding] : bindings.getAll()) { + if (!binding.enabled) { + binding.state = false; + continue; + } + binding.justChanged = false; + + bool newstate = false; + switch (binding.type) { + case InputType::KEYBOARD: + newstate = pressed(static_cast(binding.code)); + break; + case InputType::MOUSE: + newstate = clicked(static_cast(binding.code)); + break; + } + if (newstate) { + if (!binding.state) { + binding.state = true; + binding.justChanged = true; + binding.onactived.notify(); + } + } else { + if (binding.state) { + binding.state = false; + binding.justChanged = true; + } + } + } +} + +const char* SDLInput::getClipboardText() const { + return SDL_GetClipboardText(); +} + +void SDLInput::setClipboardText(const char* text) { + SDL_SetClipboardText(text); +} + +void SDLInput::startTextInput() { + SDL_StartTextInput(window.getSdlWindow()); +} +void SDLInput::stopTextInput() { + SDL_StopTextInput(window.getSdlWindow()); +} + +int SDLInput::getScroll() { + return scroll; +} + +bool SDLInput::pressed(Keycode key) const { + int keycode = static_cast(key); + if (keycode < 0 || keycode >= keys_buffer_size) { + return false; + } + return keys[keycode]; +} +bool SDLInput::jpressed(Keycode keycode) const { + return pressed(keycode) && + frames[static_cast(keycode)] == currentFrame; +} + +bool SDLInput::clicked(Mousecode code) const { + return pressed( + static_cast(mouse_keys_offset + static_cast(code)) + ); +} +bool SDLInput::jclicked(Mousecode code) const { + return clicked(code) && + frames[static_cast(code) + mouse_keys_offset] == currentFrame; +} + +CursorState SDLInput::getCursor() const { + return {isCursorLocked(), cursor, delta}; +} + +bool SDLInput::isCursorLocked() const { + return cursorLocked; +} + +void SDLInput::toggleCursor() { + cursorDrag = false; + cursorLocked = !cursorLocked; + SDL_SetWindowRelativeMouseMode(window.getSdlWindow(), cursorLocked); +} + +Bindings& SDLInput::getBindings() { + return bindings; +} + +const Bindings& SDLInput::getBindings() const { + return bindings; +} + +ObserverHandler SDLInput::addKeyCallback(Keycode key, KeyCallback callback) { + return keyCallbacks[key].add(std::move(callback)); +} + +const std::vector& SDLInput::getPressedKeys() const { + return pressedKeys; +} + +const std::vector& SDLInput::getCodepoints() const { + return codepoints; +} diff --git a/src/window/detail/SDLInput.hpp b/src/window/detail/SDLInput.hpp new file mode 100644 index 000000000..dd1ec2447 --- /dev/null +++ b/src/window/detail/SDLInput.hpp @@ -0,0 +1,60 @@ +#include + +#include "window/Window.hpp" +#include "window/input.hpp" + +class SDLWindow; + +class SDLInput final : public Input { +public: + inline static constexpr short mouse_keys_offset = 512; + inline static constexpr short keys_buffer_size = + mouse_keys_offset + sizeof(mousecodes_all) / sizeof(mousecodes_all[0]); + + SDLInput(SDLWindow& window); + + void pollEvents() override; + + const char* getClipboardText() const override; + void setClipboardText(const char* str) override; + + void startTextInput() override; + void stopTextInput() override; + + int getScroll() override; + + bool pressed(Keycode keycode) const override; + bool jpressed(Keycode keycode) const override; + + bool clicked(Mousecode mousecode) const override; + bool jclicked(Mousecode mousecode) const override; + + CursorState getCursor() const override; + + bool isCursorLocked() const override; + void toggleCursor() override; + + Bindings& getBindings() override; + + const Bindings& getBindings() const override; + + ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) override; + + const std::vector& getPressedKeys() const override; + const std::vector& getCodepoints() const override; +private: + glm::vec2 delta; + glm::vec2 cursor; + std::int32_t scroll = 0; + std::uint32_t currentFrame = 0; + std::uint32_t frames[keys_buffer_size] {}; + bool keys[keys_buffer_size] {}; + bool cursorLocked = false; + bool cursorDrag = false; + Bindings bindings; + std::vector codepoints; + std::vector pressedKeys; + std::unordered_map> keyCallbacks; + + SDLWindow& window; +}; \ No newline at end of file diff --git a/src/window/detail/SDLWindow.cpp b/src/window/detail/SDLWindow.cpp new file mode 100644 index 000000000..a0d6140a1 --- /dev/null +++ b/src/window/detail/SDLWindow.cpp @@ -0,0 +1,492 @@ +#include "window/detail/SDLWindow.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "debug/Logger.hpp" +#include "graphics/core/ImageData.hpp" +#include "graphics/core/Texture.hpp" +#include "settings.hpp" +#include "util/platform.hpp" +#include "window/Window.hpp" +#include "window/detail/SDLInput.hpp" + +static debug::Logger logger("window"); + +static std::unordered_set supported_gl_extensions; +static void init_gl_extensions_list() { + GLint numExtensions = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions); + + for (GLint i = 0; i < numExtensions; ++i) { + const char *ext = + reinterpret_cast(glGetStringi(GL_EXTENSIONS, i)); + if (ext) { + supported_gl_extensions.insert(ext); + } + } +} + +static bool is_gl_extension_supported(const char *extension) { + if (!extension || !*extension) { + return false; + } + return supported_gl_extensions.find(extension) != + supported_gl_extensions.end(); +} + +static const char *gl_error_name(int error) { + switch (error) { + case GL_DEBUG_TYPE_ERROR: + return "ERROR"; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + return "DEPRECATED_BEHAVIOR"; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + return "UNDEFINED_BEHAVIOR"; + case GL_DEBUG_TYPE_PORTABILITY: + return "PORTABILITY"; + case GL_DEBUG_TYPE_PERFORMANCE: + return "PERFORMANCE"; + case GL_DEBUG_TYPE_OTHER: + return "OTHER"; + } + return "UNKNOWN"; +} + +static const char *gl_severity_name(int severity) { + switch (severity) { + case GL_DEBUG_SEVERITY_LOW: + return "LOW"; + case GL_DEBUG_SEVERITY_MEDIUM: + return "MEDIUM"; + case GL_DEBUG_SEVERITY_HIGH: + return "HIGH"; + case GL_DEBUG_SEVERITY_NOTIFICATION: + return "NOTIFICATION"; + } + return "UNKNOWN"; +} + +static void GLAPIENTRY gl_message_callback( + GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar *message, + const void *userParam +) { + if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { + return; + } + if (!ENGINE_DEBUG_BUILD && severity != GL_DEBUG_SEVERITY_HIGH) { + return; + } + logger.warning() << "GL:" << gl_error_name(type) << ":" + << gl_severity_name(severity) << ": " << message; +} + +static bool initialize_gl(int width, int height) { + glewExperimental = GL_TRUE; + + GLenum glewErr = glewInit(); + if (glewErr != GLEW_OK) { + if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY) { + // see issue #240 + logger.warning() + << "glewInit() returned GLEW_ERROR_NO_GLX_DISPLAY; ignored"; + } else { + logger.error() << "failed to initialize GLEW:\n" + << glewGetErrorString(glewErr); + return false; + } + } + + init_gl_extensions_list(); + +#ifndef __APPLE__ + if (is_gl_extension_supported("GL_KHR_debug")) { + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(gl_message_callback, nullptr); + } +#endif + + glViewport(0, 0, width, height); + glClearColor(0.0f, 0.0f, 0.0f, 1); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + GLint maxTextureSize[1] {static_cast(Texture::MAX_RESOLUTION)}; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxTextureSize); + if (maxTextureSize[0] > 0) { + Texture::MAX_RESOLUTION = maxTextureSize[0]; + logger.info() << "max texture size is " << Texture::MAX_RESOLUTION; + } + + const GLubyte *vendor = glGetString(GL_VENDOR); + const GLubyte *renderer = glGetString(GL_RENDERER); + logger.info() << "GL Vendor: " << reinterpret_cast(vendor); + logger.info() << "GL Renderer: " + << reinterpret_cast(renderer); + logger.info() << "SDL: " << SDL_GetVersion(); + return true; +} + +SDLWindow::SDLWindow(DisplaySettings *settings, std::string title) noexcept { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { + logger.error() << "failed to initialize SDL: " << SDL_GetError(); + isSuccessfull = false; + return; + } + + SDL_WindowFlags flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; + + if (settings->windowMode.get() == + static_cast(WindowMode::FULLSCREEN)) { + flags |= SDL_WINDOW_FULLSCREEN; + } + + window = SDL_CreateWindow( + title.c_str(), settings->width.get(), settings->height.get(), flags + ); + if (!window) { + logger.error() << "failed to create SDL Window: " << SDL_GetError(); + isSuccessfull = false; + return; + } + + int width = 0; + int height = 0; + + if (!SDL_GetWindowSize(window, &width, &height)) { + logger.error() << "failed to get window size: " << SDL_GetError(); + isSuccessfull = false; + return; + } + + size = {width, height}; + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); +#ifdef __APPLE__ + SDL_GL_SetAttribute( + SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE + ); + SDL_GL_SetAttribute( + SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG + ); +#endif + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, settings->samples.get()); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + context = SDL_GL_CreateContext(window); + if (!context) { + logger.error() << "failed to create GL context: " << SDL_GetError(); + isSuccessfull = false; + return; + } + + if (!initialize_gl(width, height)) { + logger.error() << "failed to init GL: " << SDL_GetError(); + return; + } + + if (!SDL_GL_SetSwapInterval(1)) { + logger.error() << "failed to set vsync: " << SDL_GetError(); + isSuccessfull = false; + return; + } + + renderer = SDL_CreateRenderer(window, "vc_main"); + + float scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); + + logger.info() << "monitor content scale: " << scale; +} +SDLWindow::~SDLWindow() { + if (window) { + SDL_DestroyWindow(window); + } + if (context) { + if (!SDL_GL_DestroyContext(context)) { + logger.error() << "Cant destroy gl context: " << SDL_GetError(); + } + } +} + +void SDLWindow::swapBuffers() noexcept { + if (framerate > 0) { + auto elapsedTime = time() - prevSwap; + auto frameTime = 1.0 / framerate; + if (elapsedTime < frameTime) { + platform::sleep( + static_cast((frameTime - elapsedTime) * 1000) + ); + } + prevSwap = time(); + } + + if (!SDL_GL_SwapWindow(window)) [[unlikely]] { // C++20 needed + logger.error() << "Cant swap buffer: " << SDL_GetError(); + } + resetScissor(); +} +bool SDLWindow::isMaximized() const { + return (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED) != 0; +} +bool SDLWindow::isFocused() const { + Uint32 flags = SDL_GetWindowFlags(window); + return (flags & SDL_WINDOW_INPUT_FOCUS) != 0 || + (flags & SDL_WINDOW_MOUSE_FOCUS) != 0; +} +bool SDLWindow::isIconified() const { + return (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) != 0; +} + +bool SDLWindow::isShouldClose() const { + return toClose; +} +void SDLWindow::setShouldClose(bool flag) { + toClose = flag; +} + +void SDLWindow::setCursor(CursorShape shape) { + // so it method called each frame, we always recreate cursor. + // Refactor it + if (cursor) { + SDL_DestroyCursor(cursor); + cursor = nullptr; + } + + switch (shape) { + case CursorShape::ARROW: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_DEFAULT + ); + break; + case CursorShape::TEXT: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_TEXT + ); + break; + case CursorShape::CROSSHAIR: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_CROSSHAIR + ); + break; + case CursorShape::POINTER: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_POINTER + ); + break; + case CursorShape::EW_RESIZE: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_EW_RESIZE + ); + break; + case CursorShape::NS_RESIZE: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_NS_RESIZE + ); + break; + case CursorShape::NWSE_RESIZE: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_NWSE_RESIZE + ); + break; + case CursorShape::NESW_RESIZE: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_NESW_RESIZE + ); + break; + case CursorShape::ALL_RESIZE: + // ?? + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_NWSE_RESIZE + ); + break; + case CursorShape::NOT_ALLOWED: + cursor = SDL_CreateSystemCursor( + SDL_SystemCursor::SDL_SYSTEM_CURSOR_NOT_ALLOWED + ); + break; + } + if (cursor) { + SDL_SetCursor(cursor); + } +} +void SDLWindow::setMode(WindowMode mode) { + // WindowMode::WINDOWED by default have the next options as disbled + if (!SDL_SetWindowFullscreen(window, mode == WindowMode::FULLSCREEN)) { + logger.error() << "Cant toggle fullscreen window: " << SDL_GetError(); + } + if (!SDL_SetWindowBordered(window, mode == WindowMode::BORDERLESS)) { + logger.error() << "Cant toggle bordered window: " << SDL_GetError(); + } + this->mode = mode; +} + +WindowMode SDLWindow::getMode() const { + return mode; +} + +void SDLWindow::setIcon(const ImageData *image) { + if (image == nullptr) { + logger.error() << "Image is nullptr"; + return; + } + + SDL_Surface *iconSurface = SDL_CreateSurface( + image->getWidth(), image->getHeight(), SDL_PIXELFORMAT_RGBA32 + ); + + if (!iconSurface) { + logger.error() << "Failed to create surface for app icon: " + << SDL_GetError(); + return; + } + + memcpy( + iconSurface->pixels, + image->getData(), + image->getWidth() * image->getHeight() * 4 + ); + + if (!SDL_SetWindowIcon(window, iconSurface)) { + logger.error() << "Failed to set icon: " << SDL_GetError(); + } + + SDL_DestroySurface(iconSurface); +} + +void SDLWindow::pushScissor(glm::vec4 area) { + if (scissorStack.empty()) { + glEnable(GL_SCISSOR_TEST); + } + scissorStack.push(scissorArea); + + area.z += glm::ceil(area.x); + area.w += glm::ceil(area.y); + + area.x = glm::max(area.x, scissorArea.x); + area.y = glm::max(area.y, scissorArea.y); + + area.z = glm::min(area.z, scissorArea.z); + area.w = glm::min(area.w, scissorArea.w); + + if (area.z < 0.0f || area.w < 0.0f) { + glScissor(0, 0, 0, 0); + } else { + glScissor( + area.x, + size.y - area.w, + std::max(0, static_cast(glm::ceil(area.z - area.x))), + std::max(0, static_cast(glm::ceil(area.w - area.y))) + ); + } + scissorArea = area; +} + +void SDLWindow::resetScissor() { + scissorArea = glm::vec4(0.0f, 0.0f, size.x, size.y); + scissorStack = std::stack(); + glDisable(GL_SCISSOR_TEST); +} + +void SDLWindow::popScissor() { + if (scissorStack.empty()) { + logger.warning() << "extra Window::popScissor call"; + return; + } + glm::vec4 area = scissorStack.top(); + scissorStack.pop(); + if (area.z < 0.0f || area.w < 0.0f) { + glScissor(0, 0, 0, 0); + } else { + glScissor( + area.x, + size.y - area.w, + std::max(0, static_cast(area.z - area.x)), + std::max(0, static_cast(area.w - area.y)) + ); + } + if (scissorStack.empty()) { + glDisable(GL_SCISSOR_TEST); + } + scissorArea = area; +} + +double SDLWindow::time() { + return static_cast(SDL_GetTicksNS()) / 1'000'000'000; +} + +void SDLWindow::setFramerate(int framerate) { + if (!SDL_GL_SetSwapInterval(framerate == -1)) { + logger.error() << "Failed to set framerate: " << SDL_GetError(); + } + this->framerate = framerate; +} + +// todo: move somewhere +// Possible do with SDL_RenderReadPixels +std::unique_ptr SDLWindow::takeScreenshot() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + auto data = std::make_unique(size.x * size.y * 3); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(0, 0, size.x, size.y, GL_RGB, GL_UNSIGNED_BYTE, data.get()); + return std::make_unique( + ImageFormat::rgb888, size.x, size.y, data.release() + ); +} + +[[nodiscard]] bool SDLWindow::isValid() const { + return isSuccessfull; +} +[[nodiscard]] SDL_Window *SDLWindow::getSdlWindow() const { + return window; +} + +std::tuple, std::unique_ptr> Window::initialize( + DisplaySettings *settings, std::string title +) { + auto window = std::make_unique(settings, title); + if (!window->isValid()) { + return {nullptr, nullptr}; + } + auto input = std::make_unique(*window); + + return {std::move(window), std::move(input)}; +} + +void display::clear() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void display::clearDepth() { + glClear(GL_DEPTH_BUFFER_BIT); +} + +void display::setBgColor(glm::vec3 color) { + glClearColor(color.r, color.g, color.b, 1.0f); +} + +void display::setBgColor(glm::vec4 color) { + glClearColor(color.r, color.g, color.b, color.a); +} diff --git a/src/window/detail/SDLWindow.hpp b/src/window/detail/SDLWindow.hpp new file mode 100644 index 000000000..f1b32e837 --- /dev/null +++ b/src/window/detail/SDLWindow.hpp @@ -0,0 +1,60 @@ +#include +#include + +#include + +#include "window/Window.hpp" + +class SDLWindow final : public Window { +public: + SDLWindow(DisplaySettings *settings, std::string title) noexcept; + ~SDLWindow(); + SDLWindow(const SDLWindow &) = delete; + SDLWindow(SDLWindow &&) = default; + SDLWindow &operator=(const SDLWindow &) = delete; + SDLWindow &operator=(SDLWindow &&) = default; + + void swapBuffers() noexcept override; + + bool isMaximized() const override; + bool isFocused() const override; + bool isIconified() const override; + + bool isShouldClose() const override; + void setShouldClose(bool flag) override; + + void setCursor(CursorShape shape) override; + void setMode(WindowMode mode) override; + WindowMode getMode() const override; + + void setIcon(const ImageData *image) override; + + void pushScissor(glm::vec4 area) override; + void popScissor() override; + void resetScissor() override; + + double time() override; + + void setFramerate(int framerate) override; + + // todo: move somewhere + std::unique_ptr takeScreenshot() override; + + [[nodiscard]] bool isValid() const override; + [[nodiscard]] SDL_Window *getSdlWindow() const; +private: + bool isSuccessfull = true; + bool toClose = false; + bool fullscreen = false; + + double framerate = -1; + double prevSwap = 0.0; + + SDL_Window *window = nullptr; + SDL_Renderer *renderer = nullptr; + SDL_Cursor *cursor = nullptr; + SDL_GLContext context = nullptr; + + std::stack scissorStack; + glm::vec4 scissorArea {}; +}; \ No newline at end of file diff --git a/src/window/input.cpp b/src/window/input.cpp index 6feaddb3f..195f5ca82 100644 --- a/src/window/input.cpp +++ b/src/window/input.cpp @@ -1,76 +1,11 @@ -#include "input.hpp" +#include "window/input.hpp" +#include "coders/toml.hpp" #include "debug/Logger.hpp" #include "util/stringutil.hpp" -#include "data/dv.hpp" -#include -#include - -#ifdef _WIN32 -#include -#endif // _WIN32 - -static debug::Logger logger("input"); - -static std::unordered_map keycodes { - {"enter", GLFW_KEY_ENTER}, - {"space", GLFW_KEY_SPACE}, - {"backspace", GLFW_KEY_BACKSPACE}, - {"caps-lock", GLFW_KEY_CAPS_LOCK}, - {"escape", GLFW_KEY_ESCAPE}, - {"delete", GLFW_KEY_DELETE}, - {"home", GLFW_KEY_HOME}, - {"end", GLFW_KEY_END}, - {"tab", GLFW_KEY_TAB}, - {"insert", GLFW_KEY_INSERT}, - {"page-down", GLFW_KEY_PAGE_DOWN}, - {"page-up", GLFW_KEY_PAGE_UP}, - {"left-shift", GLFW_KEY_LEFT_SHIFT}, - {"right-shift", GLFW_KEY_RIGHT_SHIFT}, - {"left-ctrl", GLFW_KEY_LEFT_CONTROL}, - {"right-ctrl", GLFW_KEY_RIGHT_CONTROL}, - {"left-alt", GLFW_KEY_LEFT_ALT}, - {"right-alt", GLFW_KEY_RIGHT_ALT}, - {"left-super", GLFW_KEY_LEFT_SUPER}, - {"right-super", GLFW_KEY_RIGHT_SUPER}, - {"grave-accent", GLFW_KEY_GRAVE_ACCENT}, - {"left", GLFW_KEY_LEFT}, - {"right", GLFW_KEY_RIGHT}, - {"down", GLFW_KEY_DOWN}, - {"up", GLFW_KEY_UP}, -}; - -static std::unordered_map mousecodes { - {"left", GLFW_MOUSE_BUTTON_1}, - {"right", GLFW_MOUSE_BUTTON_2}, - {"middle", GLFW_MOUSE_BUTTON_3}, - {"side1", GLFW_MOUSE_BUTTON_4}, - {"side2", GLFW_MOUSE_BUTTON_5}, - {"side3", GLFW_MOUSE_BUTTON_6}, - {"side4", GLFW_MOUSE_BUTTON_7}, - {"side5", GLFW_MOUSE_BUTTON_8}, -}; - -static std::unordered_map keynames {}; -static std::unordered_map buttonsnames{}; - -std::string input_util::get_name(Mousecode code) { - const auto found = buttonsnames.find(static_cast(code)); - if (found == buttonsnames.end()) { - return "unknown"; - } - return found->second; -} - -std::string input_util::get_name(Keycode code) { - const auto found = keynames.find(static_cast(code)); - if (found == keynames.end()) { - return "unknown"; - } - return found->second; -} +debug::Logger logger("input"); void Binding::reset(InputType type, int code) { this->type = type; @@ -85,133 +20,6 @@ void Binding::reset(Mousecode code) { reset(InputType::MOUSE, static_cast(code)); } -void input_util::initialize() { - for (int i = 0; i <= 9; i++) { - keycodes[std::to_string(i)] = GLFW_KEY_0 + i; - } - for (int i = 0; i < 25; i++) { - keycodes["f" + std::to_string(i + 1)] = GLFW_KEY_F1 + i; - } - for (char i = 'a'; i <= 'z'; i++) { - keycodes[std::string({i})] = GLFW_KEY_A - 'a' + i; - } - for (const auto& entry : keycodes) { - keynames[entry.second] = entry.first; - } - for (const auto& entry : mousecodes) { - buttonsnames[entry.second] = entry.first; - } -} - -Keycode input_util::keycode_from(const std::string& name) { - const auto& found = keycodes.find(name); - if (found == keycodes.end()) { - return Keycode::UNKNOWN; - } - return static_cast(found->second); -} - -Mousecode input_util::mousecode_from(const std::string& name) { - const auto& found = mousecodes.find(name); - if (found == mousecodes.end()) { - return Mousecode::UNKNOWN; - } - return static_cast(found->second); -} - -std::string input_util::to_string(Keycode code) { - int icode_repr = static_cast(code); - const char* name = - glfwGetKeyName(icode_repr, glfwGetKeyScancode(icode_repr)); - if (name == nullptr) { - switch (icode_repr) { - case GLFW_KEY_TAB: - return "Tab"; - case GLFW_KEY_LEFT_CONTROL: - return "Left Ctrl"; - case GLFW_KEY_RIGHT_CONTROL: - return "Right Ctrl"; - case GLFW_KEY_LEFT_ALT: - return "Left Alt"; - case GLFW_KEY_RIGHT_ALT: - return "Right Alt"; - case GLFW_KEY_LEFT_SHIFT: - return "Left Shift"; - case GLFW_KEY_RIGHT_SHIFT: - return "Right Shift"; - case GLFW_KEY_CAPS_LOCK: - return "Caps-Lock"; - case GLFW_KEY_SPACE: - return "Space"; - case GLFW_KEY_ESCAPE: - return "Esc"; - case GLFW_KEY_ENTER: - return "Enter"; - case GLFW_KEY_UP: - return "Up"; - case GLFW_KEY_DOWN: - return "Down"; - case GLFW_KEY_LEFT: - return "Left"; - case GLFW_KEY_RIGHT: - return "Right"; - case GLFW_KEY_BACKSPACE: - return "Backspace"; - case GLFW_KEY_F1: - return "F1"; - case GLFW_KEY_F2: - return "F2"; - case GLFW_KEY_F3: - return "F3"; - case GLFW_KEY_F4: - return "F4"; - case GLFW_KEY_F5: - return "F5"; - case GLFW_KEY_F6: - return "F6"; - case GLFW_KEY_F7: - return "F7"; - case GLFW_KEY_F8: - return "F8"; - case GLFW_KEY_F9: - return "F9"; - case GLFW_KEY_F10: - return "F10"; - case GLFW_KEY_F11: - return "F11"; - case GLFW_KEY_F12: - return "F12"; - case GLFW_KEY_DELETE: - return "Delete"; - case GLFW_KEY_HOME: - return "Home"; - case GLFW_KEY_END: - return "End"; - case GLFW_KEY_LEFT_SUPER: - return "Left Super"; - case GLFW_KEY_RIGHT_SUPER: - return "Right Super"; - case GLFW_KEY_PAGE_UP: - return "Page Up"; - case GLFW_KEY_PAGE_DOWN: - return "Page Down"; - case GLFW_KEY_INSERT: - return "Insert"; - case GLFW_KEY_PRINT_SCREEN: - return "Print Screen"; - case GLFW_KEY_NUM_LOCK: - return "Num Lock"; - case GLFW_KEY_MENU: - return "Menu"; - case GLFW_KEY_PAUSE: - return "Pause"; - default: - return "Unknown"; - } - } - return std::string(name); -} - std::string input_util::to_string(Mousecode code) { switch (code) { case Mousecode::BUTTON_1: @@ -222,11 +30,10 @@ std::string input_util::to_string(Mousecode code) { return "MMB"; case Mousecode::BUTTON_4: case Mousecode::BUTTON_5: - case Mousecode::BUTTON_6: - case Mousecode::BUTTON_7: - case Mousecode::BUTTON_8: - return "XButton " + std::to_string(static_cast(code) - - static_cast(Mousecode::BUTTON_3)); + return "XButton " + std::to_string( + static_cast(code) - + static_cast(Mousecode::BUTTON_3) + ); default: return "unknown button"; } @@ -260,9 +67,8 @@ void Bindings::read(const dv::value& map, BindType bindType) { type = InputType::MOUSE; code = static_cast(input_util::mousecode_from(codename)); } else { - logger.error() - << "unknown input type: " << prefix << " (binding " - << util::quote(key) << ")"; + logger.error() << "unknown input type: " << prefix + << " (binding " << util::quote(key) << ")"; continue; } if (bindType == BindType::BIND) { @@ -274,8 +80,6 @@ void Bindings::read(const dv::value& map, BindType bindType) { } } -#include "coders/toml.hpp" - std::string Bindings::write() const { auto obj = dv::object(); for (auto& entry : bindings) { diff --git a/src/window/input.hpp b/src/window/input.hpp index 2edfe4c78..dfd5d9d5c 100644 --- a/src/window/input.hpp +++ b/src/window/input.hpp @@ -1,7 +1,8 @@ #pragma once -#include +#include #include +#include #include "util/HandlersList.hpp" @@ -9,130 +10,121 @@ namespace dv { class value; } -enum class BindType { - BIND = 0, - REBIND = 1 -}; +enum class BindType { BIND = 0, REBIND = 1 }; -/// @brief Represents glfw3 keycode values. -enum class Keycode : int { - SPACE = 32, - APOSTROPHE = 39, - COMMA = 44, +/// @brief Represents sdl scancode values. +enum class Keycode : std::int32_t { + SPACE = 44, + APOSTROPHE = 52, + COMMA = 54, MINUS = 45, - PERIOD = 46, - SLASH = 47, - NUM_0 = 48, - NUM_1 = 49, - NUM_2 = 50, - NUM_3 = 51, - NUM_4 = 52, - NUM_5 = 53, - NUM_6 = 54, - NUM_7 = 55, - NUM_8 = 56, - NUM_9 = 57, - SEMICOLON = 59, - EQUAL = 61, - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - LEFT_BRACKET = 91, - BACKSLASH = 92, - RIGHT_BRACKET = 93, - GRAVE_ACCENT = 96, - ESCAPE = 256, - ENTER = 257, - TAB = 258, - BACKSPACE = 259, - INSERT = 260, - DELETE = 261, - LEFT = 263, - RIGHT = 262, - DOWN = 264, - UP = 265, - PAGE_UP = 266, - PAGE_DOWN = 267, - HOME = 268, - END = 269, - CAPS_LOCK = 280, - NUM_LOCK = 282, - PRINT_SCREEN = 283, - PAUSE = 284, - F1 = 290, - F2 = 291, - F3 = 292, - F4 = 293, - F5 = 294, - F6 = 295, - F7 = 296, - F8 = 297, - F9 = 298, - F10 = 299, - F11 = 300, - F12 = 301, - LEFT_SHIFT = 340, - LEFT_CONTROL = 341, - LEFT_ALT = 342, - LEFT_SUPER = 343, - RIGHT_SHIFT = 344, - RIGHT_CONTROL = 345, - RIGHT_ALT = 346, - RIGHT_SUPER = 347, - MENU = 348, - UNKNOWN = -1 + PERIOD = 55, + SLASH = 56, + NUM_0 = 39, + NUM_1 = 30, + NUM_2, + NUM_3, + NUM_4, + NUM_5, + NUM_6, + NUM_7, + NUM_8, + NUM_9, + SEMICOLON = 51, + EQUAL = 46, + A = 4, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + LEFT_BRACKET = 47, + BACKSLASH = 49, + RIGHT_BRACKET = 48, + GRAVE_ACCENT = 53, + ESCAPE = 41, + ENTER = 40, + TAB = 43, + BACKSPACE = 42, + INSERT = 73, + DELETE = 76, + LEFT = 80, + RIGHT = 79, + DOWN = 81, + UP = 82, + PAGE_UP = 75, + PAGE_DOWN = 78, + HOME = 74, + END = 77, + CAPS_LOCK = 57, + NUM_LOCK = 83, + PRINT_SCREEN = 70, + PAUSE = 72, + F1 = 58, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + LEFT_SHIFT = 225, + LEFT_CONTROL = 224, + LEFT_ALT = 226, + LEFT_SUPER = 227, + RIGHT_SHIFT = 229, + RIGHT_CONTROL = 228, + RIGHT_ALT = 230, + RIGHT_SUPER = 231, + MENU = 118, + UNKNOWN = 0 }; -/// @brief Represents glfw3 mouse button IDs. -/// @details There is a subset of glfw3 mouse button IDs. +/// @brief Represents sdl mouse button IDs. +/// @details There is a subset of sdl mouse button IDs. enum class Mousecode : int { - BUTTON_1 = 0, // Left mouse button - BUTTON_2 = 1, // Right mouse button - BUTTON_3 = 2, // Middle mouse button - BUTTON_4 = 3, // Side mouse button 1 - BUTTON_5 = 4, // Side mouse button 2 - BUTTON_6 = 5, // Side mouse button 3 - BUTTON_7 = 6, // Side mouse button 4 - BUTTON_8 = 7, // Side mouse button 5 - UNKNOWN = -1, + BUTTON_1 = 1, // Left mouse button + BUTTON_2 = 2, // Right mouse button + BUTTON_3 = 3, // Middle mouse button + BUTTON_4 = 4, // Side mouse button 1 + BUTTON_5 = 5, // Side mouse button 2 + UNKNOWN = 0, }; -inline Mousecode MOUSECODES_ALL[] { +inline Mousecode mousecodes_all[] { Mousecode::BUTTON_1, Mousecode::BUTTON_2, Mousecode::BUTTON_3, Mousecode::BUTTON_4, Mousecode::BUTTON_5, - Mousecode::BUTTON_6, - Mousecode::BUTTON_7, - Mousecode::BUTTON_8}; +}; namespace input_util { void initialize(); - Keycode keycode_from(const std::string& name); Mousecode mousecode_from(const std::string& name); @@ -266,6 +258,9 @@ public: virtual const char* getClipboardText() const = 0; virtual void setClipboardText(const char* str) = 0; + virtual void startTextInput() = 0; + virtual void stopTextInput() = 0; + virtual int getScroll() = 0; virtual bool pressed(Keycode keycode) const = 0; @@ -283,10 +278,12 @@ public: virtual const Bindings& getBindings() const = 0; - virtual ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) = 0; + virtual ObserverHandler addKeyCallback( + Keycode key, KeyCallback callback + ) = 0; virtual const std::vector& getPressedKeys() const = 0; - virtual const std::vector& getCodepoints() const = 0; + virtual const std::vector& getCodepoints() const = 0; ObserverHandler addCallback(const std::string& name, KeyCallback callback) { return getBindings().require(name).onactived.add(callback); diff --git a/vcpkg.json b/vcpkg.json index 17145c4a0..8ac88a6ef 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -4,7 +4,7 @@ "dependencies": [ "opengl", "openal-soft", - "glfw3", + "sdl3", "glew", "glm", "libpng",