From 5123fb66a96e6205d486280b0f627ed733da609a Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Tue, 12 Sep 2023 20:01:37 +0200 Subject: [PATCH 001/363] Added a CI job for the SDL backend on iOS --- .github/workflows/ci.yml | 95 ++++++++++++++++++++++++++++++++++-- cmake/Modules/FindSDL2.cmake | 16 +++--- examples/iOS/CMakeLists.txt | 6 ++- 3 files changed, 104 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca3b11b03..176f34a10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -391,9 +391,9 @@ jobs: cmake --build TGUI-build-SDL_RENDERER --config Debug - - name: Build TGUI (SDL_GLES2) + - name: Build TGUI (SDL_TTF_GLES2) run: > - cmake -B TGUI-build-SDL_GLES2 -GNinja + cmake -B TGUI-build-SDL_TTF_GLES2 -GNinja -DBUILD_SHARED_LIBS=ON -DTGUI_CXX_STANDARD=20 -DTGUI_WARNINGS_AS_ERRORS=ON @@ -401,15 +401,29 @@ jobs: -DTGUI_BUILD_GUI_BUILDER=ON -DTGUI_BUILD_TESTS=OFF -DCMAKE_UNITY_BUILD=ON - -DTGUI_BACKEND=SDL_GLES2 + -DTGUI_BACKEND=SDL_TTF_GLES2 - cmake --build TGUI-build-SDL_GLES2 --config Release + cmake --build TGUI-build-SDL_TTF_GLES2 --config Release + + - name: Build TGUI (SDL_OPENGL3) + run: > + cmake -B TGUI-build-SDL_OPENGL3 -GNinja + -DBUILD_SHARED_LIBS=ON + -DTGUI_CXX_STANDARD=23 + -DTGUI_WARNINGS_AS_ERRORS=ON + -DTGUI_BUILD_EXAMPLES=ON + -DTGUI_BUILD_GUI_BUILDER=ON + -DTGUI_BUILD_TESTS=OFF + -DCMAKE_UNITY_BUILD=ON + -DTGUI_BACKEND=SDL_OPENGL3 + + cmake --build TGUI-build-SDL_OPENGL3 --config Release - name: Build TGUI (GLFW_OPENGL3) run: > cmake -B TGUI-build-GLFW_OPENGL3 -GNinja -DBUILD_SHARED_LIBS=ON - -DTGUI_CXX_STANDARD=23 + -DTGUI_CXX_STANDARD=17 -DTGUI_WARNINGS_AS_ERRORS=ON -DTGUI_BUILD_EXAMPLES=ON -DTGUI_BUILD_GUI_BUILDER=ON @@ -826,6 +840,77 @@ jobs: #---------------------------------------- + ios-sdl: + runs-on: macos-12 + env: + SDL_VERSION: 2.26.5 + SDL_TTF_VERSION: 2.0.18 + steps: + - name: Checkout TGUI + uses: actions/checkout@v3 + + - name: Build SDL + run: | + wget -nv https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.zip + unzip SDL2-$SDL_VERSION.zip + xcodebuild -project SDL2-$SDL_VERSION/Xcode/SDL/SDL.xcodeproj -scheme "Static Library-iOS" CONFIGURATION_BUILD_DIR="$GITHUB_WORKSPACE/SDL-iOS-libs" + + - name: Build SDL_ttf + run: | + wget -nv https://github.com/libsdl-org/SDL_ttf/releases/download/release-$SDL_TTF_VERSION/SDL2_ttf-$SDL_TTF_VERSION.zip + unzip SDL2_ttf-$SDL_TTF_VERSION.zip + xcodebuild -project SDL2_ttf-$SDL_TTF_VERSION/Xcode/SDL_ttf.xcodeproj -scheme "Static Library" CONFIGURATION_BUILD_DIR="$GITHUB_WORKSPACE/SDL-iOS-libs" + + - name: Build TGUI (SDL_RENDERER) + run: > + cmake -B TGUI-build-iOS-SDL_RENDERER + -GXcode + -DCMAKE_SYSTEM_NAME=iOS + -DCMAKE_OSX_ARCHITECTURES=arm64 + -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 + -DCMAKE_INSTALL_PREFIX=TGUI_INSTALL_SDL_RENDERER + -DTGUI_BACKEND=SDL_RENDERER + -DTGUI_SHARED_LIBS=OFF + -DCMAKE_UNITY_BUILD=ON + -DTGUI_CXX_STANDARD=17 + -DTGUI_WARNINGS_AS_ERRORS=ON + -DTGUI_BUILD_EXAMPLES=OFF + -DSDL2_LIBRARY="$GITHUB_WORKSPACE/SDL-iOS-libs/libSDL2.a" + -DSDL2_INCLUDE_DIR="$GITHUB_WORKSPACE/SDL2-$SDL_VERSION/include" + -DSDL2_TTF_LIBRARY="$GITHUB_WORKSPACE/SDL-iOS-libs/libSDL2_ttf.a" + -DSDL2_TTF_INCLUDE_DIR="$GITHUB_WORKSPACE/SDL2_ttf-$SDL_TTF_VERSION" + -DTGUI_SKIP_SDL_CONFIG=TRUE + + cmake --build TGUI-build-iOS-SDL_RENDERER --config Release --target install + + test -e TGUI_INSTALL_SDL_RENDERER/lib/libtgui-s.a + + - name: Build TGUI (SDL_TTF_GLES2) + run: > + cmake -B TGUI-build-iOS-SDL_TTF_GLES2 + -GXcode + -DCMAKE_SYSTEM_NAME=iOS + -DCMAKE_OSX_ARCHITECTURES=arm64 + -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 + -DCMAKE_INSTALL_PREFIX=TGUI_INSTALL_SDL_TTF_GLES2 + -DTGUI_BACKEND=SDL_TTF_GLES2 + -DTGUI_SHARED_LIBS=OFF + -DCMAKE_UNITY_BUILD=ON + -DTGUI_CXX_STANDARD=17 + -DTGUI_WARNINGS_AS_ERRORS=ON + -DTGUI_BUILD_EXAMPLES=OFF + -DSDL2_LIBRARY="$GITHUB_WORKSPACE/SDL-iOS-libs/libSDL2.a" + -DSDL2_INCLUDE_DIR="$GITHUB_WORKSPACE/SDL2-$SDL_VERSION/include" + -DSDL2_TTF_LIBRARY="$GITHUB_WORKSPACE/SDL-iOS-libs/libSDL2_ttf.a" + -DSDL2_TTF_INCLUDE_DIR="$GITHUB_WORKSPACE/SDL2_ttf-$SDL_TTF_VERSION" + -DTGUI_SKIP_SDL_CONFIG=TRUE + + cmake --build TGUI-build-iOS-SDL_TTF_GLES2 --config Release --target install + + test -e TGUI_INSTALL_SDL_TTF_GLES2/lib/libtgui-s.a + + #---------------------------------------- + # TODO: How to put this in a separate workflow file? # The idea was to have the following at the top: # on: diff --git a/cmake/Modules/FindSDL2.cmake b/cmake/Modules/FindSDL2.cmake index 325549910..efcae598a 100644 --- a/cmake/Modules/FindSDL2.cmake +++ b/cmake/Modules/FindSDL2.cmake @@ -270,7 +270,7 @@ if(SDL2_LIBRARY) # I think it has something to do with the CACHE STRING. # So I use a temporary variable until the end so I can set the # "real" variable in one-shot. - if(APPLE) + if(APPLE AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") set(SDL2_LIBRARIES ${SDL2_LIBRARIES} -framework Cocoa) endif() @@ -349,10 +349,12 @@ if(SDL2_FOUND) INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}") if(APPLE) - # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. - # For more details, please see above. - set_property(TARGET SDL2::SDL2 APPEND PROPERTY - INTERFACE_LINK_OPTIONS -framework Cocoa) + if (NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") + # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. + # For more details, please see above. + set_property(TARGET SDL2::SDL2 APPEND PROPERTY + INTERFACE_LINK_OPTIONS -framework Cocoa) + endif() else() # For threads, as mentioned Apple doesn't need this. # For more details, please see above. @@ -366,7 +368,9 @@ if(SDL2_FOUND) IMPORTED_LOCATION "${SDL2_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}") if(APPLE) - set_property(TARGET SDL2::Core APPEND PROPERTY INTERFACE_LINK_OPTIONS -framework Cocoa) + if (NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") + set_property(TARGET SDL2::Core APPEND PROPERTY INTERFACE_LINK_OPTIONS -framework Cocoa) + endif() else() set_property(TARGET SDL2::Core APPEND PROPERTY INTERFACE_LINK_LIBRARIES Threads::Threads) endif() diff --git a/examples/iOS/CMakeLists.txt b/examples/iOS/CMakeLists.txt index 1dc31c369..21fcfec76 100644 --- a/examples/iOS/CMakeLists.txt +++ b/examples/iOS/CMakeLists.txt @@ -7,11 +7,13 @@ set_source_files_properties(${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Reso macro(tgui_add_ios_example backend) tgui_add_example(ios_demo_${backend} SOURCES iOS-example-${backend}.cpp ${RESOURCES}) - # The app needs an identifier and signing to work correctly - set_property(TARGET ios_demo_${backend} PROPERTY XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer") set_target_properties(ios_demo_${backend} PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "eu.tgui.ios_demo" MACOSX_BUNDLE_BUNDLE_NAME "ios_demo_${backend}" + XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" + XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" + #XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer" + #XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "" ) endmacro() From 722696eff8e8c20f304c4c42f60fb4a9718d58df Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sat, 16 Sep 2023 23:12:48 +0200 Subject: [PATCH 002/363] If an IMM/IME is active then place the candidate window next to the caret of EditBox or TextArea on Windows (see #219) --- include/TGUI/Backend/Window/Backend.hpp | 8 +- include/TGUI/Backend/Window/BackendGui.hpp | 26 ++++ .../Backend/Window/GLFW/BackendGuiGLFW.hpp | 20 +++- .../TGUI/Backend/Window/SDL/BackendGuiSDL.hpp | 29 +++++ .../TGUI/Backend/Window/SDL/BackendSDL.hpp | 8 +- .../Backend/Window/SFML/BackendGuiSFML.hpp | 36 +++++- .../TGUI/Backend/Window/SFML/BackendSFML.hpp | 8 +- include/TGUI/Global.hpp | 9 ++ include/TGUI/Keyboard.hpp | 16 ++- include/TGUI/WindowsIMM.hpp | 94 +++++++++++++++ src/Backend/Window/Backend.cpp | 4 +- src/Backend/Window/BackendGui.cpp | 18 +++ src/Backend/Window/GLFW/BackendGuiGLFW.cpp | 40 +++++++ src/Backend/Window/SDL/BackendGuiSDL.cpp | 103 +++++++++++++++- src/Backend/Window/SDL/BackendSDL.cpp | 4 +- src/Backend/Window/SFML/BackendGuiSFML.cpp | 56 +++++++++ src/Backend/Window/SFML/BackendSFML.cpp | 4 +- src/CMakeLists.txt | 1 + src/Widgets/EditBox.cpp | 33 +++-- src/Widgets/TextArea.cpp | 48 ++++++-- src/WindowsIMM.cpp | 113 ++++++++++++++++++ 21 files changed, 633 insertions(+), 45 deletions(-) create mode 100644 include/TGUI/WindowsIMM.hpp create mode 100644 src/WindowsIMM.cpp diff --git a/include/TGUI/Backend/Window/Backend.hpp b/include/TGUI/Backend/Window/Backend.hpp index 8b6a2981b..a81549e52 100644 --- a/include/TGUI/Backend/Window/Backend.hpp +++ b/include/TGUI/Backend/Window/Backend.hpp @@ -217,14 +217,14 @@ TGUI_MODULE_EXPORT namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// virtual void setMouseCursor(BackendGui* gui, Cursor::Type type) = 0; - +#ifndef TGUI_REMOVE_DEPRECATED_CODE ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Opens the virtual keyboard on Android and iOS /// @param inputRect Part of the screen where the text input is located /// /// If this function isn't overriden then calling it does nothing. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - virtual void openVirtualKeyboard(const FloatRect& inputRect); + TGUI_DEPRECATED("Use BackendGui::startTextInput instead") virtual void openVirtualKeyboard(const FloatRect& inputRect); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -232,8 +232,8 @@ TGUI_MODULE_EXPORT namespace tgui /// /// If this function isn't overriden then calling it does nothing. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - virtual void closeVirtualKeyboard(); - + TGUI_DEPRECATED("Use BackendGui::stopTextInput instead") virtual void closeVirtualKeyboard(); +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Checks the state for one of the modifier keys diff --git a/include/TGUI/Backend/Window/BackendGui.hpp b/include/TGUI/Backend/Window/BackendGui.hpp index 1509f4e2c..45c136c4a 100644 --- a/include/TGUI/Backend/Window/BackendGui.hpp +++ b/include/TGUI/Backend/Window/BackendGui.hpp @@ -603,6 +603,32 @@ TGUI_MODULE_EXPORT namespace tgui virtual void mainLoop(Color clearColor = {240, 240, 240}) = 0; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when focusing a text field (EditBox or TextArea). + /// It may result in the software keyboard being opened. + /// + /// @param inputRect The rectangle where text is being inputted + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + virtual void startTextInput(FloatRect inputRect); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when unfocusing a text field (EditBox or TextArea). + /// It may result in the software keyboard being closed. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + virtual void stopTextInput(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when the position of the caret changes in a text field (EditBox or TextArea). + /// If an IME is used then this function may move the IME candidate list to the text cursor position. + /// + /// @param inputRect The rectangle where text is being inputted + /// @param caretPos Location of the text cursor, relative to the gui view + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + virtual void updateTextCursorPosition(FloatRect inputRect, Vector2f caretPos); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: diff --git a/include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp b/include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp index 766b2f9b5..8752ad625 100644 --- a/include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp +++ b/include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp @@ -53,7 +53,13 @@ TGUI_MODULE_EXPORT namespace tgui /// /// @warning setGuiWindow has to be called by the subclass that inherits from this base class before the gui is used ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - BackendGuiGLFW() = default; + BackendGuiGLFW(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Destructor + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ~BackendGuiGLFW(); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -224,6 +230,18 @@ TGUI_MODULE_EXPORT namespace tgui GLFWwindow* getWindow() const; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when the position of the caret changes in a text field (EditBox or TextArea). + /// If an IME is used then this function may move the IME candidate list to the text cursor position. + /// + /// @param inputRect The rectangle where text is being inputted + /// @param caretPos Location of the text cursor, relative to the gui view + /// + /// This function currently only has effect on Windows. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateTextCursorPosition(FloatRect inputRect, Vector2f caretPos) override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: diff --git a/include/TGUI/Backend/Window/SDL/BackendGuiSDL.hpp b/include/TGUI/Backend/Window/SDL/BackendGuiSDL.hpp index 58b221af9..280fb68e4 100644 --- a/include/TGUI/Backend/Window/SDL/BackendGuiSDL.hpp +++ b/include/TGUI/Backend/Window/SDL/BackendGuiSDL.hpp @@ -125,6 +125,32 @@ TGUI_MODULE_EXPORT namespace tgui SDL_Window* getWindow() const; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when focusing a text field (EditBox or TextArea). + /// It may result in the software keyboard being opened. + /// + /// @param inputRect The rectangle where text is being inputted + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void startTextInput(FloatRect inputRect) override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when unfocusing a text field (EditBox or TextArea). + /// It may result in the software keyboard being closed. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void stopTextInput() override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when the position of the caret changes in a text field (EditBox or TextArea). + /// If an IME is used then this function may move the IME candidate list to the text cursor position. + /// + /// @param inputRect The rectangle where text is being inputted + /// @param caretPos Location of the text cursor, relative to the gui view + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateTextCursorPosition(FloatRect inputRect, Vector2f caretPos) override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: @@ -163,6 +189,9 @@ TGUI_MODULE_EXPORT namespace tgui SDL_Window* m_window = nullptr; float m_dpiScale = 1; + bool m_textInputStarted = false; + SDL_Rect m_textInputRect = {}; + bool m_touchFirstFingerDown = false; SDL_FingerID m_touchFirstFingerId = 0; // Only valid if m_touchFirstFingerDown is true SDL_TouchID m_touchFirstFingerTouchId = 0; // Only valid if m_touchFirstFingerDown is true diff --git a/include/TGUI/Backend/Window/SDL/BackendSDL.hpp b/include/TGUI/Backend/Window/SDL/BackendSDL.hpp index cc32b6dba..72b7c5cc7 100644 --- a/include/TGUI/Backend/Window/SDL/BackendSDL.hpp +++ b/include/TGUI/Backend/Window/SDL/BackendSDL.hpp @@ -99,19 +99,19 @@ TGUI_MODULE_EXPORT namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void setMouseCursor(BackendGui* gui, Cursor::Type type) override; - +#ifndef TGUI_REMOVE_DEPRECATED_CODE ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Opens the virtual keyboard on Android and iOS /// @param inputRect Part of the screen where the text input is located ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void openVirtualKeyboard(const FloatRect& inputRect) override; + TGUI_DEPRECATED("Use BackendGui::startTextInput instead") void openVirtualKeyboard(const FloatRect& inputRect) override; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Closes the virtual keyboard on Android and iOS ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void closeVirtualKeyboard() override; - + TGUI_DEPRECATED("Use BackendGui::stopTextInput instead") void closeVirtualKeyboard() override; +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Checks the state for one of the modifier keys diff --git a/include/TGUI/Backend/Window/SFML/BackendGuiSFML.hpp b/include/TGUI/Backend/Window/SFML/BackendGuiSFML.hpp index b690859f0..074f5080a 100644 --- a/include/TGUI/Backend/Window/SFML/BackendGuiSFML.hpp +++ b/include/TGUI/Backend/Window/SFML/BackendGuiSFML.hpp @@ -50,7 +50,13 @@ TGUI_MODULE_EXPORT namespace tgui /// /// @warning setGuiWindow has to be called by the subclass that inherits from this base class before the gui is used ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - BackendGuiSFML() = default; + BackendGuiSFML(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Destructor + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ~BackendGuiSFML(); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -121,6 +127,34 @@ TGUI_MODULE_EXPORT namespace tgui sf::Window* getWindow() const; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when focusing a text field (EditBox or TextArea). + /// It will result in the software keyboard being opened on Android and iOS. + /// + /// @param inputRect The rectangle where text is being inputted + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void startTextInput(FloatRect inputRect) override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when unfocusing a text field (EditBox or TextArea). + /// It will result in the software keyboard being closed on Android and iOS. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void stopTextInput() override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when the position of the caret changes in a text field (EditBox or TextArea). + /// If an IME is used then this function may move the IME candidate list to the text cursor position. + /// + /// @param inputRect The rectangle where text is being inputted + /// @param caretPos Location of the text cursor, relative to the gui view + /// + /// This function currently only has effect on Windows. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateTextCursorPosition(FloatRect inputRect, Vector2f caretPos) override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: diff --git a/include/TGUI/Backend/Window/SFML/BackendSFML.hpp b/include/TGUI/Backend/Window/SFML/BackendSFML.hpp index 4de8ed4c3..a39f21c99 100644 --- a/include/TGUI/Backend/Window/SFML/BackendSFML.hpp +++ b/include/TGUI/Backend/Window/SFML/BackendSFML.hpp @@ -95,19 +95,19 @@ TGUI_MODULE_EXPORT namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void setMouseCursor(BackendGui* gui, Cursor::Type type) override; - +#ifndef TGUI_REMOVE_DEPRECATED_CODE ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Opens the virtual keyboard on Android and iOS /// @param inputRect Part of the screen where the text input is located ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void openVirtualKeyboard(const FloatRect& inputRect) override; + TGUI_DEPRECATED("Use BackendGui::startTextInput instead") void openVirtualKeyboard(const FloatRect& inputRect) override; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Closes the virtual keyboard on Android and iOS ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void closeVirtualKeyboard() override; - + TGUI_DEPRECATED("Use BackendGui::stopTextInput instead") void closeVirtualKeyboard() override; +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Checks the state for one of the modifier keys diff --git a/include/TGUI/Global.hpp b/include/TGUI/Global.hpp index 872b7e4ec..f09c907c7 100644 --- a/include/TGUI/Global.hpp +++ b/include/TGUI/Global.hpp @@ -63,6 +63,15 @@ TGUI_MODULE_EXPORT namespace tgui } #endif + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Clamps a value between two boundries. This function exists because std::clamp was only added with c++17 + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + template + TGUI_NODISCARD constexpr const T& clamp(const T& v, const T& lo, const T& hi) + { + return (v < lo) ? lo : (hi < v) ? hi : v; + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Sets the default text size for all widgets created after calling the function diff --git a/include/TGUI/Keyboard.hpp b/include/TGUI/Keyboard.hpp index c2c29e89b..03b72cba0 100644 --- a/include/TGUI/Keyboard.hpp +++ b/include/TGUI/Keyboard.hpp @@ -42,8 +42,8 @@ namespace tgui namespace keyboard { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - inline void openVirtualKeyboard(const Widget* requestingWidget, FloatRect inputRect) +#ifndef TGUI_REMOVE_DEPRECATED_CODE + TGUI_DEPRECATED("Use BackendGui::startTextInput instead") inline void openVirtualKeyboard(const Widget* requestingWidget, FloatRect inputRect) { const Widget* widget = requestingWidget; while (widget) @@ -91,9 +91,11 @@ namespace tgui inputRect = {topLeftPos, bottomRightPos - topLeftPos}; } + TGUI_IGNORE_DEPRECATED_WARNINGS_START getBackend()->openVirtualKeyboard(inputRect); + TGUI_IGNORE_DEPRECATED_WARNINGS_END } - +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TGUI_NODISCARD inline bool isShiftPressed(const Event::KeyEvent& event) @@ -109,12 +111,14 @@ namespace tgui } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - inline void closeVirtualKeyboard() +#ifndef TGUI_REMOVE_DEPRECATED_CODE + TGUI_DEPRECATED("Use BackendGui::stopTextInput instead") inline void closeVirtualKeyboard() { + TGUI_IGNORE_DEPRECATED_WARNINGS_START getBackend()->closeVirtualKeyboard(); + TGUI_IGNORE_DEPRECATED_WARNINGS_END } - +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TGUI_NODISCARD inline bool isMultiselectModifierPressed(const Event::KeyEvent& event) diff --git a/include/TGUI/WindowsIMM.hpp b/include/TGUI/WindowsIMM.hpp new file mode 100644 index 000000000..ccc353afb --- /dev/null +++ b/include/TGUI/WindowsIMM.hpp @@ -0,0 +1,94 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2023 Bruno Van de Velde (vdv_b@tgui.eu) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +#ifndef TGUI_WINDOWS_IMM_HPP +#define TGUI_WINDOWS_IMM_HPP + +#include + +#include + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +TGUI_MODULE_EXPORT namespace tgui +{ + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Dynamically loads and uses imm32.dll on Windows to control the IME + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + class TGUI_API WindowsIMM + { + public: + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // @brief Loads the imm32.dll and the functions that this class might access + // + // If this function is called multiple times then the resources are reused. + // + // @warning Every call to initialize() must have a corresponding call to release() + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + static void initialize(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // @brief Releases the resources from imm32.dll + // + // A reference counter was incremented on each initialize() call, this function does nothing until the count reaches 0. + // + // @warning This function must not be called without calling initialize() first + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + static void release(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief This function is called by TGUI when the position of the cursor changes in a text field (EditBox or TextArea). + /// It may result in the IME window moving. + /// + /// @param hWnd Handle to the window that contains the focused widget + /// @param caretPos Location of the text cursor, relative to the top-left of the window, in pixels + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + static void setCandidateWindowPosition(HWND hWnd, Vector2f caretPos); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + private: + + using ImmGetContextFunc = HIMC(WINAPI*)(HWND); + using ImmSetCompositionWindowFunc = BOOL(WINAPI*)(HIMC, LPCOMPOSITIONFORM); + using ImmReleaseContextFunc = BOOL(WINAPI*)(HWND, HIMC); + + static ImmGetContextFunc m_dllImmGetContext; + static ImmSetCompositionWindowFunc m_dllImmSetCompositionWindow; + static ImmReleaseContextFunc m_dllImmReleaseContext; + + static HMODULE m_dllImmModuleHandle; + + static unsigned int m_referenceCount; + }; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // TGUI_WINDOWS_IMM_HPP diff --git a/src/Backend/Window/Backend.cpp b/src/Backend/Window/Backend.cpp index 34b323a37..1a20518f6 100644 --- a/src/Backend/Window/Backend.cpp +++ b/src/Backend/Window/Backend.cpp @@ -147,7 +147,7 @@ namespace tgui } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - +#ifndef TGUI_REMOVE_DEPRECATED_CODE void Backend::openVirtualKeyboard(const FloatRect&) { } @@ -157,7 +157,7 @@ namespace tgui void Backend::closeVirtualKeyboard() { } - +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Backend::setClipboard(const String& contents) diff --git a/src/Backend/Window/BackendGui.cpp b/src/Backend/Window/BackendGui.cpp index 96259039c..24f3ff108 100644 --- a/src/Backend/Window/BackendGui.cpp +++ b/src/Backend/Window/BackendGui.cpp @@ -626,6 +626,24 @@ namespace tgui } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BackendGui::startTextInput(FloatRect) + { + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BackendGui::stopTextInput() + { + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BackendGui::updateTextCursorPosition(FloatRect, Vector2f) + { + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/Backend/Window/GLFW/BackendGuiGLFW.cpp b/src/Backend/Window/GLFW/BackendGuiGLFW.cpp index ec7a569c1..591c1e2d0 100644 --- a/src/Backend/Window/GLFW/BackendGuiGLFW.cpp +++ b/src/Backend/Window/GLFW/BackendGuiGLFW.cpp @@ -30,11 +30,19 @@ import tgui; #else #include + + #ifdef TGUI_SYSTEM_WINDOWS + #include + #endif #endif #define GLFW_INCLUDE_NONE // Don't let GLFW include an OpenGL extention loader #include +#define GLFW_EXPOSE_NATIVE_WIN32 +#define GLFW_NATIVE_INCLUDE_NONE +#include + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace tgui @@ -154,6 +162,24 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + BackendGuiGLFW::BackendGuiGLFW() + { +#ifdef TGUI_SYSTEM_WINDOWS + WindowsIMM::initialize(); +#endif + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + BackendGuiGLFW::~BackendGuiGLFW() + { +#ifdef TGUI_SYSTEM_WINDOWS + WindowsIMM::release(); +#endif + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool BackendGuiGLFW::windowFocusCallback(int focused) { const auto event = convertWindowFocusEvent(focused); @@ -407,6 +433,20 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BackendGuiGLFW::updateTextCursorPosition(FloatRect, Vector2f caretPos) + { +#ifdef TGUI_SYSTEM_WINDOWS + if (!m_window) + return; + + WindowsIMM::setCandidateWindowPosition(glfwGetWin32Window(m_window), mapCoordsToPixel(caretPos)); +#else + BackendGui::updateTextCursorPosition(caretPos); +#endif + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BackendGuiGLFW::setGuiWindow(GLFWwindow* window) { TGUI_ASSERT(std::dynamic_pointer_cast(getBackend()), "BackendGuiGLFW requires system backend of type BackendGLFW"); diff --git a/src/Backend/Window/SDL/BackendGuiSDL.cpp b/src/Backend/Window/SDL/BackendGuiSDL.cpp index 42cd10144..f921e620f 100644 --- a/src/Backend/Window/SDL/BackendGuiSDL.cpp +++ b/src/Backend/Window/SDL/BackendGuiSDL.cpp @@ -208,13 +208,15 @@ namespace tgui } case SDL_EVENT_TEXT_INPUT: { - const String& unicodeStr(static_cast(eventSDL.text.text)); + const String unicodeStr(static_cast(eventSDL.text.text)); if (unicodeStr.empty()) return false; - // This code assumes eventSDL.text.text never contains more than one UTF-32 character + // This code assumes eventSDL.text.text never contains more than one UTF-32 character. + // If there are more characters (which can happen when an IME is used) then this + // will be handled in BackendGuiSDL::handleEvent. eventTGUI.type = Event::Type::TextEntered; - eventTGUI.text.unicode = unicodeStr[0]; + eventTGUI.text.unicode = unicodeStr.back(); return true; } case SDL_EVENT_KEY_DOWN: @@ -408,6 +410,27 @@ namespace tgui handleEvent(mouseMoveEvent); } + // If a text event consists of multiple unicode characters (which can happen when an IME is used) then our + // converted event only contains the last character. We will send all other unicode characters here. + if ((event.type == Event::Type::TextEntered) && (sdlEvent.type == SDL_EVENT_TEXT_INPUT) && (sdlEvent.text.text[1] != '\0')) + { + // Note that we also pass here if sdlEvent.text.text consists of multiple UTF-8 characters that still fit + // within a single UTF-32 codepoint. So we still need to check that there are multiple UTF-32 codepoints below. + const String unicodeStr(static_cast(sdlEvent.text.text)); + if (unicodeStr.length() >= 2) + { + // We send all characters except the last one here, as that one is part of the TGUI event + // that will be processed at the end of this function. + Event textEvent; + textEvent.type = Event::Type::TextEntered; + for (unsigned int i = 0; i < unicodeStr.length() - 1; ++i) + { + textEvent.text.unicode = unicodeStr[i]; + handleEvent(textEvent); + } + } + } + return handleEvent(event); } @@ -504,6 +527,80 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BackendGuiSDL::startTextInput(FloatRect inputRect) + { + if (!m_textInputStarted) + { + // We need to call SDL_StartTextInput here, but we reuse the updateTextCursorPosition code as it needs the same calculations + updateTextCursorPosition(inputRect, {}); + + SDL_StartTextInput(); + m_textInputStarted = true; + + // For some weird reason the IME candidates window might remain on the old location when we move focus from + // one widget to another (even though SDL_StopTextInput was even called before we got here). The solution seems + // to be to call SDL_SetTextInputRect here again, after SDL_StartTextInput was called. + // We don't need to do this explicitly here because updateTextCursorPosition will be called shortly after + // startTextInput finishes, which already makes another SDL_SetTextInputRect call. + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BackendGuiSDL::stopTextInput() + { + if (m_textInputStarted) + { + SDL_StopTextInput(); + m_textInputStarted = false; + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BackendGuiSDL::updateTextCursorPosition(FloatRect inputRect, Vector2f caretPos) + { + // We only support high-DPI in this code with SDL 2.26 or newer. + // The rectangle passed to SDL_SetTextInputRect will be wrong in older SDL versions on high-DPI screens. + float dpiScale = 1; + +#if (SDL_MAJOR_VERSION > 2) || ((SDL_MAJOR_VERSION == 2) && (SDL_MINOR_VERSION >= 26)) + // This code was originally added for iOS (https://github.com/texus/TGUI/issues/183). + // Apparently it isn't needed for the calculation of the IME window position on Windows, so maybe this code isn't correct anymore? + // Until more information is available, the code is disabled now except for when running on iOS. + #ifdef TGUI_SYSTEM_IOS + // On a high-DPI screen, we work in pixel coordinates while the SDL rectangle needs to be provided in screen coordinates. + // So we need to calculate the DPI scaling of the window. + if (m_window) + { + Vector2i windowSizeScreenCoords; + SDL_GetWindowSize(m_window, &windowSizeScreenCoords.x, &windowSizeScreenCoords.y); + + Vector2i windowSizePixels; + SDL_GetWindowSizeInPixels(m_window, &windowSizePixels.x, &windowSizePixels.y); + + if ((windowSizeScreenCoords.y != 0) && (windowSizeScreenCoords.y != windowSizePixels.y)) + dpiScale = static_cast(windowSizePixels.y) / static_cast(windowSizeScreenCoords.y); + } + #endif +#endif + + const Vector2f topLeft = mapCoordsToPixel(inputRect.getPosition()); + const Vector2f bottomRight = mapCoordsToPixel(inputRect.getPosition() + inputRect.getSize()); + const Vector2f caretPosPixels = mapCoordsToPixel(caretPos); + + // SDL positions the IME window at the left side of the input rect, but we want the window to follow the caret. + // So we set the left side of the rectangle to the current location of the caret. + SDL_Rect sdlRect; + sdlRect.x = static_cast(std::round(std::max(caretPosPixels.x, topLeft.x) / dpiScale)); + sdlRect.y = static_cast(std::round(topLeft.y / dpiScale)); + sdlRect.w = static_cast(std::round(std::max(caretPosPixels.x, (bottomRight - topLeft).x) / dpiScale)); + sdlRect.h = static_cast(std::round((bottomRight - topLeft).y / dpiScale)); + SDL_SetTextInputRect(&sdlRect); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BackendGuiSDL::setGuiWindow(SDL_Window* window) { TGUI_ASSERT(std::dynamic_pointer_cast(getBackend()), "BackendGuiSDL requires system backend of type BackendSDL"); diff --git a/src/Backend/Window/SDL/BackendSDL.cpp b/src/Backend/Window/SDL/BackendSDL.cpp index 0a440bfe3..f05b1525e 100644 --- a/src/Backend/Window/SDL/BackendSDL.cpp +++ b/src/Backend/Window/SDL/BackendSDL.cpp @@ -164,7 +164,7 @@ namespace tgui } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - +#ifndef TGUI_REMOVE_DEPRECATED_CODE void BackendSDL::openVirtualKeyboard(const FloatRect& rect) { // We only support high-DPI in this code with SDL 2.26 or newer. @@ -214,7 +214,7 @@ namespace tgui { SDL_StopTextInput(); } - +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool BackendSDL::isKeyboardModifierPressed(Event::KeyModifier modifierKey) diff --git a/src/Backend/Window/SFML/BackendGuiSFML.cpp b/src/Backend/Window/SFML/BackendGuiSFML.cpp index 020825a5f..322324e8e 100644 --- a/src/Backend/Window/SFML/BackendGuiSFML.cpp +++ b/src/Backend/Window/SFML/BackendGuiSFML.cpp @@ -30,6 +30,10 @@ #else #include #include + + #ifdef TGUI_SYSTEM_WINDOWS + #include + #endif #endif #if !TGUI_EXPERIMENTAL_USE_STD_MODULE @@ -155,6 +159,24 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + BackendGuiSFML::BackendGuiSFML() + { +#ifdef TGUI_SYSTEM_WINDOWS + WindowsIMM::initialize(); +#endif + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + BackendGuiSFML::~BackendGuiSFML() + { +#ifdef TGUI_SYSTEM_WINDOWS + WindowsIMM::release(); +#endif + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool BackendGuiSFML::handleEvent(sf::Event sfmlEvent) { // Detect scrolling with two fingers by examining touch events @@ -417,6 +439,40 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BackendGuiSFML::startTextInput(FloatRect) + { + // Open the software keyboard on Android and iOS + sf::Keyboard::setVirtualKeyboardVisible(true); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BackendGuiSFML::stopTextInput() + { + // Open the software keyboard on Android and iOS + sf::Keyboard::setVirtualKeyboardVisible(false); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BackendGuiSFML::updateTextCursorPosition(FloatRect, Vector2f caretPos) + { +#ifdef TGUI_SYSTEM_WINDOWS + if (!m_window) + return; + + #if SFML_VERSION_MAJOR >= 3 + WindowsIMM::setCandidateWindowPosition(m_window->getNativeHandle(), mapCoordsToPixel(caretPos)); + #else + WindowsIMM::setCandidateWindowPosition(m_window->getSystemHandle(), mapCoordsToPixel(caretPos)); + #endif +#else + BackendGui::updateTextCursorPosition(caretPos); +#endif + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BackendGuiSFML::setGuiWindow(sf::Window& window) { TGUI_ASSERT(std::dynamic_pointer_cast(getBackend()), "BackendGuiSFML requires system backend of type BackendSFML"); diff --git a/src/Backend/Window/SFML/BackendSFML.cpp b/src/Backend/Window/SFML/BackendSFML.cpp index 32d56a329..4a9c33471 100644 --- a/src/Backend/Window/SFML/BackendSFML.cpp +++ b/src/Backend/Window/SFML/BackendSFML.cpp @@ -142,7 +142,7 @@ namespace tgui } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - +#ifndef TGUI_REMOVE_DEPRECATED_CODE void BackendSFML::openVirtualKeyboard(const FloatRect&) { sf::Keyboard::setVirtualKeyboardVisible(true); @@ -154,7 +154,7 @@ namespace tgui { sf::Keyboard::setVirtualKeyboardVisible(false); } - +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool BackendSFML::isKeyboardModifierPressed(Event::KeyModifier modifierKey) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a4783f902..c0b21ac77 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,6 +49,7 @@ set(TGUI_SRC Transform.cpp TwoFingerScrollDetect.cpp Widget.cpp + WindowsIMM.cpp Backend/Font/BackendFont.cpp Backend/Renderer/BackendRenderTarget.cpp Backend/Renderer/BackendText.cpp diff --git a/src/Widgets/EditBox.cpp b/src/Widgets/EditBox.cpp index 7b81c025f..3fe72bb64 100644 --- a/src/Widgets/EditBox.cpp +++ b/src/Widgets/EditBox.cpp @@ -391,6 +391,18 @@ namespace tgui void EditBox::setFocused(bool focused) { + if (m_parentGui) + { + if (focused) + { + const auto absoluteInnerPos = getAbsolutePosition(m_bordersCached.getOffset()); + const FloatRect inputRect = {absoluteInnerPos, getAbsolutePosition(getInnerSize()) - absoluteInnerPos}; + m_parentGui->startTextInput(inputRect); + } + else + m_parentGui->stopTextInput(); + } + if (focused) { m_caretVisible = true; @@ -406,13 +418,6 @@ namespace tgui onReturnOrUnfocus.emit(this, m_text); } -#if defined (TGUI_SYSTEM_ANDROID) || defined (TGUI_SYSTEM_IOS) - if (focused) - keyboard::openVirtualKeyboard(this, {{}, getSize()}); - else - keyboard::closeVirtualKeyboard(); -#endif - ClickableWidget::setFocused(focused); } @@ -1135,6 +1140,20 @@ namespace tgui // Set the position of the caret caretLeft += m_textFull.findCharacterPos(m_selEnd).x - (m_caret.getSize().x * 0.5f); m_caret.setPosition({caretLeft, m_paddingCached.getTop()}); + + if (m_parentGui) + { + // The SDL backend will place the IME candidate window below the input rectangle, but for some reason, when the first + // character is typed it places the window inside the rectangle at the top position. Instead of making the entire + // edit box our rectangle, we will only select the area of the text itself, to reduce the distance that the window + // position jumps between its initial and later positions. Other backends (SFML and GLFW) currently ignore the rectangle. + const Vector2f textPos = {m_bordersCached.getLeft() + m_paddingCached.getLeft(), textY}; + const auto absoluteTextPos = getAbsolutePosition(textPos); + const Vector2f innerSize = {getSize().x - m_bordersCached.getLeft() - m_bordersCached.getRight() - m_paddingCached.getLeft() - m_paddingCached.getRight(), + getSize().y - m_bordersCached.getTop() - m_bordersCached.getTop() - m_paddingCached.getBottom() - m_paddingCached.getBottom()}; + const FloatRect inputRect = {absoluteTextPos, getAbsolutePosition({textPos.x, textPos.y + m_textFull.getSize().y}) - absoluteTextPos}; + m_parentGui->updateTextCursorPosition(inputRect, getAbsolutePosition({caretLeft + m_caret.getSize().x, textY})); + } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/Widgets/TextArea.cpp b/src/Widgets/TextArea.cpp index 8e88f9e75..9ae667233 100644 --- a/src/Widgets/TextArea.cpp +++ b/src/Widgets/TextArea.cpp @@ -399,10 +399,35 @@ namespace tgui void TextArea::setFocused(bool focused) { + const Vector2f caretPosition = {m_caretPosition.x + m_bordersCached.getLeft() + m_paddingCached.getLeft() - static_cast(m_horizontalScrollbar->getValue()), + m_caretPosition.y + m_bordersCached.getTop() + m_paddingCached.getTop() - static_cast(m_verticalScrollbar->getValue())}; + + // The SDL backend will positions the IME candidate window outside the rectangle, so if we pass the entire + // size of the text area then it could appear far from the cursor. We want the window to be below the line + // that our cursor is on. For some reason the IME window is only placed at the correct location after the + // second character is typed, when the first character is typed the location is always at the top position + // of the rectangle instead of below the rectangle. So we choose the top of the rectangle as the top of the line + // instead of the top of the widget, so that distance between the initial and final window locations isn't huge. + // Other backends (SFML and GLFW) currently ignore the rectangle. + const auto absoluteLineTopPos = getAbsolutePosition({ 0, caretPosition.y }); + const float caretHeight = std::max(m_fontCached.getFontHeight(m_textSizeCached), m_lineHeight); + const FloatRect inputRect = {absoluteLineTopPos, getAbsolutePosition({getSize().x, caretPosition.y + caretHeight}) - absoluteLineTopPos}; + + if (m_parentGui) + { + if (focused) + m_parentGui->startTextInput(inputRect); + else + m_parentGui->stopTextInput(); + } + if (focused) { m_caretVisible = true; m_animationTimeElapsed = {}; + + if (m_parentGui) + m_parentGui->updateTextCursorPosition(inputRect, getAbsolutePosition({caretPosition.x + m_caretWidthCached, caretPosition.y})); } else // Unfocusing { @@ -414,13 +439,6 @@ namespace tgui } } -#if defined (TGUI_SYSTEM_ANDROID) || defined (TGUI_SYSTEM_IOS) - if (focused) - keyboard::openVirtualKeyboard(this, {{}, getSize()}); - else - keyboard::closeVirtualKeyboard(); -#endif - Widget::setFocused(focused); } @@ -1601,7 +1619,9 @@ namespace tgui if ((m_selEnd.x > 0) && (m_selEnd.x < m_lines[m_selEnd.y].length())) kerning = m_fontCached.getKerning(m_lines[m_selEnd.y][m_selEnd.x - 1], m_lines[m_selEnd.y][m_selEnd.x], m_textSizeCached, false); - m_caretPosition = {textOffset + tempText.findCharacterPos(tempText.getString().length()).x + kerning, static_cast(m_selEnd.y) * m_lineHeight}; + const float caretLeft = textOffset + tempText.findCharacterPos(tempText.getString().length()).x + kerning; + const float caretTop = static_cast(m_selEnd.y) * m_lineHeight; + m_caretPosition = {caretLeft, caretTop}; } if (m_horizontalScrollbarPolicy != Scrollbar::Policy::Never) @@ -1681,7 +1701,7 @@ namespace tgui // Calculate the position of the text objects m_selectionRects.clear(); m_textBeforeSelection.setPosition({textOffset, 0}); - m_defaultText.setPosition({ textOffset, 0 }); + m_defaultText.setPosition({textOffset, 0}); if (m_selStart != m_selEnd) { @@ -1765,6 +1785,16 @@ namespace tgui } } + if (m_parentGui) + { + const Vector2f caretPosition = {m_caretPosition.x + m_bordersCached.getLeft() + m_paddingCached.getLeft() - static_cast(m_horizontalScrollbar->getValue()), + m_caretPosition.y + m_bordersCached.getTop() + m_paddingCached.getTop() - static_cast(m_verticalScrollbar->getValue())}; + const auto absoluteLineTopPos = getAbsolutePosition({0, caretPosition.y}); + const float caretHeight = std::max(m_fontCached.getFontHeight(m_textSizeCached), m_lineHeight); + const FloatRect inputRect = {absoluteLineTopPos, getAbsolutePosition({getSize().x, caretPosition.y + caretHeight}) - absoluteLineTopPos}; + m_parentGui->updateTextCursorPosition(inputRect, getAbsolutePosition({caretPosition.x + m_caretWidthCached, caretPosition.y})); + } + recalculateVisibleLines(); } diff --git a/src/WindowsIMM.cpp b/src/WindowsIMM.cpp new file mode 100644 index 000000000..b8eb33568 --- /dev/null +++ b/src/WindowsIMM.cpp @@ -0,0 +1,113 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2023 Bruno Van de Velde (vdv_b@tgui.eu) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include + +#ifdef TGUI_SYSTEM_WINDOWS +#include + +#if !TGUI_EXPERIMENTAL_USE_STD_MODULE + #include +#endif + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace tgui +{ + WindowsIMM::ImmGetContextFunc WindowsIMM::m_dllImmGetContext = nullptr; + WindowsIMM::ImmSetCompositionWindowFunc WindowsIMM::m_dllImmSetCompositionWindow = nullptr; + WindowsIMM::ImmReleaseContextFunc WindowsIMM::m_dllImmReleaseContext = nullptr; + + HMODULE WindowsIMM::m_dllImmModuleHandle = nullptr; + + unsigned int WindowsIMM::m_referenceCount = 0; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void WindowsIMM::initialize() + { + ++m_referenceCount; + if (m_referenceCount != 1) + return; + + m_dllImmModuleHandle = LoadLibraryW(L"imm32.dll"); + if (m_dllImmModuleHandle) + { +#if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + m_dllImmGetContext = reinterpret_cast(GetProcAddress(m_dllImmModuleHandle, "ImmGetContext")); + m_dllImmSetCompositionWindow = reinterpret_cast(GetProcAddress(m_dllImmModuleHandle, "ImmSetCompositionWindow")); + m_dllImmReleaseContext = reinterpret_cast(GetProcAddress(m_dllImmModuleHandle, "ImmReleaseContext")); +#if defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void WindowsIMM::release() + { + TGUI_ASSERT(m_referenceCount > 0, "WindowsIMM::release can't be called without a corresponding WindowsIMM::initialize() call!"); + --m_referenceCount; + if (m_referenceCount > 0) + return; + + if (m_dllImmModuleHandle) + { + FreeLibrary(m_dllImmModuleHandle); + m_dllImmModuleHandle = nullptr; + m_dllImmGetContext = nullptr; + m_dllImmSetCompositionWindow = nullptr; + m_dllImmReleaseContext = nullptr; + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void WindowsIMM::setCandidateWindowPosition(HWND hWnd, Vector2f caretPos) + { + if (!m_dllImmGetContext || !m_dllImmSetCompositionWindow || !m_dllImmReleaseContext) + return; + + if (HIMC hIMC = m_dllImmGetContext(hWnd)) + { + COMPOSITIONFORM compositionForm; + compositionForm.dwStyle = CFS_FORCE_POSITION; + compositionForm.ptCurrentPos.x = static_cast(std::round(caretPos.x)); + compositionForm.ptCurrentPos.y = static_cast(std::round(caretPos.y)); + compositionForm.rcArea = {}; + m_dllImmSetCompositionWindow(hIMC, &compositionForm); + + m_dllImmReleaseContext(hWnd, hIMC); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} +#endif + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From d615d4b8b24d1cab49d9abdc7d2a3e1e844833fc Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sat, 16 Sep 2023 23:27:57 +0200 Subject: [PATCH 003/363] Fixed compile error in last commit --- src/Backend/Window/SFML/BackendGuiSFML.cpp | 9 ++++++--- src/Widgets/EditBox.cpp | 2 -- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Backend/Window/SFML/BackendGuiSFML.cpp b/src/Backend/Window/SFML/BackendGuiSFML.cpp index 322324e8e..716d6bfe9 100644 --- a/src/Backend/Window/SFML/BackendGuiSFML.cpp +++ b/src/Backend/Window/SFML/BackendGuiSFML.cpp @@ -455,9 +455,9 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef TGUI_SYSTEM_WINDOWS void BackendGuiSFML::updateTextCursorPosition(FloatRect, Vector2f caretPos) { -#ifdef TGUI_SYSTEM_WINDOWS if (!m_window) return; @@ -466,10 +466,13 @@ namespace tgui #else WindowsIMM::setCandidateWindowPosition(m_window->getSystemHandle(), mapCoordsToPixel(caretPos)); #endif + } #else - BackendGui::updateTextCursorPosition(caretPos); -#endif + void BackendGuiSFML::updateTextCursorPosition(FloatRect inputRect, Vector2f caretPos) + { + BackendGui::updateTextCursorPosition(inputRect, caretPos); } +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/Widgets/EditBox.cpp b/src/Widgets/EditBox.cpp index 3fe72bb64..739b75f83 100644 --- a/src/Widgets/EditBox.cpp +++ b/src/Widgets/EditBox.cpp @@ -1149,8 +1149,6 @@ namespace tgui // position jumps between its initial and later positions. Other backends (SFML and GLFW) currently ignore the rectangle. const Vector2f textPos = {m_bordersCached.getLeft() + m_paddingCached.getLeft(), textY}; const auto absoluteTextPos = getAbsolutePosition(textPos); - const Vector2f innerSize = {getSize().x - m_bordersCached.getLeft() - m_bordersCached.getRight() - m_paddingCached.getLeft() - m_paddingCached.getRight(), - getSize().y - m_bordersCached.getTop() - m_bordersCached.getTop() - m_paddingCached.getBottom() - m_paddingCached.getBottom()}; const FloatRect inputRect = {absoluteTextPos, getAbsolutePosition({textPos.x, textPos.y + m_textFull.getSize().y}) - absoluteTextPos}; m_parentGui->updateTextCursorPosition(inputRect, getAbsolutePosition({caretLeft + m_caret.getSize().x, textY})); } From 3911e77d647c1788401f73d1fdbeab45f9479627 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sat, 16 Sep 2023 23:38:13 +0200 Subject: [PATCH 004/363] Code still didn't compile on non-Windows platforms --- src/Backend/Window/GLFW/BackendGuiGLFW.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Backend/Window/GLFW/BackendGuiGLFW.cpp b/src/Backend/Window/GLFW/BackendGuiGLFW.cpp index 591c1e2d0..6c64adae7 100644 --- a/src/Backend/Window/GLFW/BackendGuiGLFW.cpp +++ b/src/Backend/Window/GLFW/BackendGuiGLFW.cpp @@ -39,9 +39,11 @@ #define GLFW_INCLUDE_NONE // Don't let GLFW include an OpenGL extention loader #include -#define GLFW_EXPOSE_NATIVE_WIN32 -#define GLFW_NATIVE_INCLUDE_NONE -#include +#ifdef TGUI_SYSTEM_WINDOWS + #define GLFW_EXPOSE_NATIVE_WIN32 + #define GLFW_NATIVE_INCLUDE_NONE + #include +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -432,19 +434,20 @@ namespace tgui } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - +#ifdef TGUI_SYSTEM_WINDOWS void BackendGuiGLFW::updateTextCursorPosition(FloatRect, Vector2f caretPos) { -#ifdef TGUI_SYSTEM_WINDOWS if (!m_window) return; WindowsIMM::setCandidateWindowPosition(glfwGetWin32Window(m_window), mapCoordsToPixel(caretPos)); + } #else - BackendGui::updateTextCursorPosition(caretPos); -#endif + void BackendGuiGLFW::updateTextCursorPosition(FloatRect inputRect, Vector2f caretPos) + { + BackendGui::updateTextCursorPosition(inputRect, caretPos); } - +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void BackendGuiGLFW::setGuiWindow(GLFWwindow* window) From 5e28bc104f3933c7f1c329701eede8d1a8a54849 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sat, 16 Sep 2023 23:39:09 +0200 Subject: [PATCH 005/363] Place the dll files from the nightly builds inside a bin folder instead of the lib folder, like it is done for the other precompiled downloads --- .github/workflows/ci.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 176f34a10..460c129ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -928,7 +928,7 @@ jobs: # I've decided to just put the nightly build code in this file as well. nightly-build-windows-visual-studio: if: github.event_name == 'push' && github.ref == 'refs/heads/1.x' - needs: [linux, linux-latest-dev, linux-oldest, linux-per-backend, windows-sfml-sdl, windows-oldest, windows-clang, windows-static-mt, macos, android-sdl, android-sfml-graphics, ios-sfml-graphics] + needs: [linux, linux-latest-dev, linux-oldest, linux-per-backend, windows-sfml-sdl, windows-oldest, windows-clang, windows-static-mt, macos, android-sdl, android-sfml-graphics, ios-sfml-graphics, ios-sdl] runs-on: windows-2019 env: SFML_VERSION: 2.6.0 @@ -1031,13 +1031,14 @@ jobs: run: | echo d | xcopy /s TGUI TGUI-1.x-nightly mkdir TGUI-1.x-nightly\lib + mkdir TGUI-1.x-nightly\bin move TGUI-build-static-x86\lib\Release\tgui-s.lib TGUI-1.x-nightly\lib\ move TGUI-build-static-x86\lib\Debug\tgui-s-d.lib TGUI-1.x-nightly\lib\ move TGUI-build-static-x86\lib\Debug\tgui-s-d.pdb TGUI-1.x-nightly\lib\ move TGUI-build-dynamic-x86\lib\Release\tgui.lib TGUI-1.x-nightly\lib\ - move TGUI-build-dynamic-x86\lib\Release\tgui.dll TGUI-1.x-nightly\lib\ + move TGUI-build-dynamic-x86\lib\Release\tgui.dll TGUI-1.x-nightly\bin\ move TGUI-build-dynamic-x86\lib\Debug\tgui-d.lib TGUI-1.x-nightly\lib\ - move TGUI-build-dynamic-x86\lib\Debug\tgui-d.dll TGUI-1.x-nightly\lib\ + move TGUI-build-dynamic-x86\lib\Debug\tgui-d.dll TGUI-1.x-nightly\bin\ move TGUI-build-dynamic-x86\lib\Debug\tgui-d.pdb TGUI-1.x-nightly\lib\ move TGUI-build-static-x86\doc\html TGUI-1.x-nightly\doc\ 7z a -tzip TGUI-1.x-nightly-VisualStudio-32bit-for-SFML-${env:SFML_VERSION}.zip TGUI-1.x-nightly @@ -1047,13 +1048,14 @@ jobs: run: | echo d | xcopy /s TGUI TGUI-1.x-nightly mkdir TGUI-1.x-nightly\lib + mkdir TGUI-1.x-nightly\bin move TGUI-build-static-x64\lib\Release\tgui-s.lib TGUI-1.x-nightly\lib\ move TGUI-build-static-x64\lib\Debug\tgui-s-d.lib TGUI-1.x-nightly\lib\ move TGUI-build-static-x64\lib\Debug\tgui-s-d.pdb TGUI-1.x-nightly\lib\ move TGUI-build-dynamic-x64\lib\Release\tgui.lib TGUI-1.x-nightly\lib\ - move TGUI-build-dynamic-x64\lib\Release\tgui.dll TGUI-1.x-nightly\lib\ + move TGUI-build-dynamic-x64\lib\Release\tgui.dll TGUI-1.x-nightly\bin\ move TGUI-build-dynamic-x64\lib\Debug\tgui-d.lib TGUI-1.x-nightly\lib\ - move TGUI-build-dynamic-x64\lib\Debug\tgui-d.dll TGUI-1.x-nightly\lib\ + move TGUI-build-dynamic-x64\lib\Debug\tgui-d.dll TGUI-1.x-nightly\bin\ move TGUI-build-dynamic-x64\lib\Debug\tgui-d.pdb TGUI-1.x-nightly\lib\ move TGUI-build-static-x64\doc\html TGUI-1.x-nightly\doc\ 7z a -tzip TGUI-1.x-nightly-VisualStudio-64bit-for-SFML-${env:SFML_VERSION}.zip TGUI-1.x-nightly From 612e73b645fd40de9308aa48e87e1010624e35c5 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sat, 16 Sep 2023 23:59:14 +0200 Subject: [PATCH 006/363] Fixed a warning in VS2017 --- include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp | 4 ++++ include/TGUI/Backend/Window/SDL/BackendSDL.hpp | 4 ++++ include/TGUI/Backend/Window/SFML/BackendSFML.hpp | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp b/include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp index 8752ad625..f8a765db9 100644 --- a/include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp +++ b/include/TGUI/Backend/Window/GLFW/BackendGuiGLFW.hpp @@ -40,6 +40,8 @@ using GLFWwindow = struct GLFWwindow; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TGUI_IGNORE_DEPRECATED_WARNINGS_START // Required for VS2017 due to inheriting a function that we deprecated + TGUI_MODULE_EXPORT namespace tgui { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -268,6 +270,8 @@ TGUI_MODULE_EXPORT namespace tgui }; } +TGUI_IGNORE_DEPRECATED_WARNINGS_END + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endif // TGUI_BACKEND_GUI_GLFW_HPP diff --git a/include/TGUI/Backend/Window/SDL/BackendSDL.hpp b/include/TGUI/Backend/Window/SDL/BackendSDL.hpp index 72b7c5cc7..eac3f1d24 100644 --- a/include/TGUI/Backend/Window/SDL/BackendSDL.hpp +++ b/include/TGUI/Backend/Window/SDL/BackendSDL.hpp @@ -42,6 +42,8 @@ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TGUI_IGNORE_DEPRECATED_WARNINGS_START // Required for VS2017 due to inheriting a function that we deprecated + TGUI_MODULE_EXPORT namespace tgui { class TGUI_API BackendSDL : public Backend @@ -187,6 +189,8 @@ TGUI_MODULE_EXPORT namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } +TGUI_IGNORE_DEPRECATED_WARNINGS_END + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endif // TGUI_BACKEND_SDL_HPP diff --git a/include/TGUI/Backend/Window/SFML/BackendSFML.hpp b/include/TGUI/Backend/Window/SFML/BackendSFML.hpp index a39f21c99..636e5c6c7 100644 --- a/include/TGUI/Backend/Window/SFML/BackendSFML.hpp +++ b/include/TGUI/Backend/Window/SFML/BackendSFML.hpp @@ -44,6 +44,8 @@ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TGUI_IGNORE_DEPRECATED_WARNINGS_START // Required for VS2017 due to inheriting a function that we deprecated + TGUI_MODULE_EXPORT namespace tgui { class TGUI_API BackendSFML : public Backend @@ -199,6 +201,8 @@ TGUI_MODULE_EXPORT namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } +TGUI_IGNORE_DEPRECATED_WARNINGS_END + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endif // TGUI_BACKEND_SFML_HPP From 9e879f3ce12d1eca8b80894f5b7b2a872afd5b3e Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sun, 17 Sep 2023 11:51:53 +0200 Subject: [PATCH 007/363] CI coverage reports weren't being uploaded anymore --- .github/workflows/ci.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 460c129ee..52b3495b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,6 +91,8 @@ jobs: steps: - name: Checkout TGUI uses: actions/checkout@v3 + with: + path: TGUI - name: Lookup dependency versions id: find-dependencies @@ -165,9 +167,10 @@ jobs: cmake --build GLFW-build --config Debug --target install - name: Build TGUI + working-directory: TGUI run: > CXXFLAGS="-fprofile-arcs -ftest-coverage -DTGUI_REMOVE_DEPRECATED_CODE" - cmake -B TGUI-build + cmake -B build -DSFML_DIR="$GITHUB_WORKSPACE/SFML_INSTALL/lib/cmake/SFML/" -DSDL3_DIR="$GITHUB_WORKSPACE/SDL_INSTALL/lib/cmake/SDL3/" -DSDL3_ttf_DIR="$GITHUB_WORKSPACE/SDL_TTF_INSTALL/lib/cmake/SDL3_ttf/" @@ -192,10 +195,10 @@ jobs: -DTGUI_HAS_BACKEND_GLFW_OPENGL3=ON -DTGUI_HAS_BACKEND_GLFW_GLES2=ON - make -C TGUI-build -j$(nproc) + make -C build -j$(nproc) - name: Run tests - working-directory: TGUI-build/tests + working-directory: TGUI/build/tests run: | set -a && source ../../tests/EnableSoftwareRenderer.env && set +a xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SFML_GRAPHICS @@ -209,7 +212,16 @@ jobs: xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=GLFW_GLES2 - name: Upload coverage reports to Codecov - run: bash <(curl -s https://codecov.io/bash) -p TGUI-build -s . -a "-s . -pr" + working-directory: TGUI + run: | + curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --import + curl -Os https://uploader.codecov.io/latest/linux/codecov + curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM + curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig + gpg --verify codecov.SHA256SUM.sig codecov.SHA256SUM + shasum -a 256 -c codecov.SHA256SUM + chmod +x codecov + ./codecov -ga "-pr" -t ${{secrets.CODECOV_TOKEN}} #---------------------------------------- From fb1f07c4b6d173a1dba5f4e87a77290af73feaff Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sun, 17 Sep 2023 18:24:52 +0200 Subject: [PATCH 008/363] Added onToggle and draw tests for ToggleButton --- tests/Widgets/ToggleButton.cpp | 290 +++++++++++++++++- .../ToggleButton_Disabled_DisabledSet.png | Bin 0 -> 1382 bytes .../ToggleButton_Disabled_NormalSet.png | Bin 0 -> 1801 bytes ...ggleButton_Disabled_TextureDisabledSet.png | Bin 0 -> 3063 bytes ...ToggleButton_Disabled_TextureNormalSet.png | Bin 0 -> 3400 bytes .../ToggleButton_DownHover_DownSet.png | Bin 0 -> 1488 bytes .../ToggleButton_DownHover_HoverSet.png | Bin 0 -> 1597 bytes .../ToggleButton_DownHover_NormalSet.png | Bin 0 -> 1801 bytes .../ToggleButton_DownHover_TextureDownSet.png | Bin 0 -> 2920 bytes ...ToggleButton_DownHover_TextureHoverSet.png | Bin 0 -> 3127 bytes ...oggleButton_DownHover_TextureNormalSet.png | Bin 0 -> 3400 bytes tests/expected/ToggleButton_Down_DownSet.png | Bin 0 -> 1488 bytes tests/expected/ToggleButton_Down_HoverSet.png | Bin 0 -> 1597 bytes .../expected/ToggleButton_Down_NormalSet.png | Bin 0 -> 1801 bytes .../ToggleButton_Down_TextureDownSet.png | Bin 0 -> 2920 bytes .../ToggleButton_Down_TextureHoverSet.png | Bin 0 -> 3127 bytes .../ToggleButton_Down_TextureNormalSet.png | Bin 0 -> 3400 bytes tests/expected/ToggleButton_Hover_DownSet.png | Bin 0 -> 1597 bytes .../expected/ToggleButton_Hover_HoverSet.png | Bin 0 -> 1597 bytes .../expected/ToggleButton_Hover_NormalSet.png | Bin 0 -> 1801 bytes .../ToggleButton_Hover_TextureDownSet.png | Bin 0 -> 3127 bytes .../ToggleButton_Hover_TextureHoverSet.png | Bin 0 -> 3127 bytes .../ToggleButton_Hover_TextureNormalSet.png | Bin 0 -> 3400 bytes .../expected/ToggleButton_Normal_DownSet.png | Bin 0 -> 1801 bytes .../expected/ToggleButton_Normal_HoverSet.png | Bin 0 -> 1801 bytes .../ToggleButton_Normal_NormalSet.png | Bin 0 -> 1801 bytes .../expected/ToggleButton_Normal_Outline.png | Bin 0 -> 3106 bytes .../ToggleButton_Normal_TextureDownSet.png | Bin 0 -> 3400 bytes .../ToggleButton_Normal_TextureHoverSet.png | Bin 0 -> 3400 bytes .../ToggleButton_Normal_TextureNormalSet.png | Bin 0 -> 3400 bytes 30 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 tests/expected/ToggleButton_Disabled_DisabledSet.png create mode 100644 tests/expected/ToggleButton_Disabled_NormalSet.png create mode 100644 tests/expected/ToggleButton_Disabled_TextureDisabledSet.png create mode 100644 tests/expected/ToggleButton_Disabled_TextureNormalSet.png create mode 100644 tests/expected/ToggleButton_DownHover_DownSet.png create mode 100644 tests/expected/ToggleButton_DownHover_HoverSet.png create mode 100644 tests/expected/ToggleButton_DownHover_NormalSet.png create mode 100644 tests/expected/ToggleButton_DownHover_TextureDownSet.png create mode 100644 tests/expected/ToggleButton_DownHover_TextureHoverSet.png create mode 100644 tests/expected/ToggleButton_DownHover_TextureNormalSet.png create mode 100644 tests/expected/ToggleButton_Down_DownSet.png create mode 100644 tests/expected/ToggleButton_Down_HoverSet.png create mode 100644 tests/expected/ToggleButton_Down_NormalSet.png create mode 100644 tests/expected/ToggleButton_Down_TextureDownSet.png create mode 100644 tests/expected/ToggleButton_Down_TextureHoverSet.png create mode 100644 tests/expected/ToggleButton_Down_TextureNormalSet.png create mode 100644 tests/expected/ToggleButton_Hover_DownSet.png create mode 100644 tests/expected/ToggleButton_Hover_HoverSet.png create mode 100644 tests/expected/ToggleButton_Hover_NormalSet.png create mode 100644 tests/expected/ToggleButton_Hover_TextureDownSet.png create mode 100644 tests/expected/ToggleButton_Hover_TextureHoverSet.png create mode 100644 tests/expected/ToggleButton_Hover_TextureNormalSet.png create mode 100644 tests/expected/ToggleButton_Normal_DownSet.png create mode 100644 tests/expected/ToggleButton_Normal_HoverSet.png create mode 100644 tests/expected/ToggleButton_Normal_NormalSet.png create mode 100644 tests/expected/ToggleButton_Normal_Outline.png create mode 100644 tests/expected/ToggleButton_Normal_TextureDownSet.png create mode 100644 tests/expected/ToggleButton_Normal_TextureHoverSet.png create mode 100644 tests/expected/ToggleButton_Normal_TextureNormalSet.png diff --git a/tests/Widgets/ToggleButton.cpp b/tests/Widgets/ToggleButton.cpp index c94260820..54d9266cb 100644 --- a/tests/Widgets/ToggleButton.cpp +++ b/tests/Widgets/ToggleButton.cpp @@ -83,7 +83,43 @@ TEST_CASE("[ToggleButton]") testClickableWidgetSignals(button); } - // TODO: Toggled signal + SECTION("Toggled signal") + { + button->setPosition(40, 30); + button->setSize(150, 100); + + unsigned int toggleCount = 0; + button->onToggle(&genericCallback, std::ref(toggleCount)); + + SECTION("mouse click") + { + button->leftMouseReleased({115, 80}); + REQUIRE(toggleCount == 0); + + button->leftMousePressed({115, 80}); + REQUIRE(toggleCount == 0); + + button->leftMouseReleased({115, 80}); + REQUIRE(toggleCount == 1); + } + + SECTION("key pressed") + { + tgui::Event::KeyEvent keyEvent; + keyEvent.alt = false; + keyEvent.control = false; + keyEvent.shift = false; + keyEvent.system = false; + + keyEvent.code = tgui::Event::KeyboardKey::Space; + button->keyPressed(keyEvent); + REQUIRE(toggleCount == 1); + + keyEvent.code = tgui::Event::KeyboardKey::Enter; + button->keyPressed(keyEvent); + REQUIRE(toggleCount == 2); + } + } } testWidgetRenderer(button->getRenderer()); @@ -363,5 +399,255 @@ TEST_CASE("[ToggleButton]") testSavingWidget("ToggleButton", button); } - // TODO: Draw tests + SECTION("Draw") + { + TEST_DRAW_INIT(120, 35, button) + + button->setEnabled(true); + button->setPosition(10, 5); + button->setSize(100, 25); + button->setText("Click me!"); + button->setTextSize(16); + + tgui::ButtonRenderer renderer = tgui::RendererData::create(); + renderer.setTextColor(tgui::Color::Red); + renderer.setBackgroundColor(tgui::Color::Green); + renderer.setBorderColor(tgui::Color::Blue); + renderer.setTextStyle(tgui::TextStyle::Italic); + renderer.setBorders({1, 2, 3, 4}); + renderer.setOpacity(0.7f); + button->setRenderer(renderer.getData()); + + auto setHoverRenderer = [&](bool textured){ + renderer.setTextColorHover(tgui::Color::Magenta); + renderer.setBackgroundColorHover(tgui::Color::Cyan); + renderer.setBorderColorHover(tgui::Color::Yellow); + renderer.setTextStyleHover(tgui::TextStyle::Bold); + if (textured) + renderer.setTextureHover("resources/Texture2.png"); + }; + + auto setDownRenderer = [&](bool textured){ + renderer.setTextColorDown(tgui::Color::Black); + renderer.setBackgroundColorDown(tgui::Color::White); + renderer.setBorderColorDown({128, 128, 128}); + renderer.setTextStyleDown(tgui::TextStyle::Underlined); + renderer.setTextColorDisabled({128, 128, 0}); + renderer.setBackgroundColorDisabled({0, 128, 128}); + renderer.setBorderColorDisabled({128, 0, 128}); + renderer.setTextStyleDisabled(tgui::TextStyle::StrikeThrough); + if (textured) + renderer.setTextureDown("resources/Texture3.png"); + }; + + auto setDisabledRenderer = [&](bool textured){ + renderer.setTextColorDisabled({128, 128, 0}); + renderer.setBackgroundColorDisabled({0, 128, 128}); + renderer.setBorderColorDisabled({128, 0, 128}); + renderer.setTextStyleDisabled(tgui::TextStyle::StrikeThrough); + if (textured) + renderer.setTextureDisabled("resources/Texture4.png"); + }; + + const auto mousePos = button->getPosition() + (button->getSize() / 2.f); + + SECTION("Outline") + { + renderer.setTextOutlineThickness(1); + renderer.setTextOutlineColor(tgui::Color::White); + TEST_DRAW("ToggleButton_Normal_Outline.png") + } + + SECTION("Colored") + { + SECTION("NormalState") + { + TEST_DRAW("ToggleButton_Normal_NormalSet.png") + + SECTION("HoverSet") + { + setHoverRenderer(false); + TEST_DRAW("ToggleButton_Normal_HoverSet.png") + + SECTION("DownSet") + { + setDownRenderer(false); + TEST_DRAW("ToggleButton_Normal_DownSet.png") + } + } + } + + SECTION("HoverState") + { + button->mouseMoved(mousePos); + + TEST_DRAW("ToggleButton_Hover_NormalSet.png") + + SECTION("HoverSet") + { + setHoverRenderer(false); + TEST_DRAW("ToggleButton_Hover_HoverSet.png") + + SECTION("DownSet") + { + setDownRenderer(false); + TEST_DRAW("ToggleButton_Hover_DownSet.png") + } + } + } + + SECTION("DownState") + { + button->mouseMoved(mousePos); + button->leftMousePressed(mousePos); + button->leftMouseReleased(mousePos); + button->mouseMoved({0, 0}); + + TEST_DRAW("ToggleButton_Down_NormalSet.png") + + SECTION("HoverSet") + { + setHoverRenderer(false); + TEST_DRAW("ToggleButton_Down_HoverSet.png") + + SECTION("DownSet") + { + setDownRenderer(false); + TEST_DRAW("ToggleButton_Down_DownSet.png") + } + } + } + + SECTION("DownHoverState") + { + button->mouseMoved(mousePos); + button->leftMousePressed(mousePos); + button->leftMouseReleased(mousePos); + + TEST_DRAW("ToggleButton_DownHover_NormalSet.png") + + SECTION("HoverSet") + { + setHoverRenderer(false); + TEST_DRAW("ToggleButton_DownHover_HoverSet.png") + + SECTION("DownSet") + { + setDownRenderer(false); + TEST_DRAW("ToggleButton_DownHover_DownSet.png") + } + } + } + + SECTION("DisabledState") + { + button->setEnabled(false); + + TEST_DRAW("ToggleButton_Disabled_NormalSet.png") + + SECTION("DisabledSet") + { + setDisabledRenderer(false); + TEST_DRAW("ToggleButton_Disabled_DisabledSet.png") + } + } + } + + SECTION("Textured") + { + renderer.setTexture("resources/Texture1.png"); + + SECTION("NormalState") + { + TEST_DRAW("ToggleButton_Normal_TextureNormalSet.png") + + SECTION("HoverSet") + { + setHoverRenderer(true); + TEST_DRAW("ToggleButton_Normal_TextureHoverSet.png") + + SECTION("DownSet") + { + setDownRenderer(true); + TEST_DRAW("ToggleButton_Normal_TextureDownSet.png") + } + } + } + + SECTION("HoverState") + { + button->mouseMoved(mousePos); + + TEST_DRAW("ToggleButton_Hover_TextureNormalSet.png") + + SECTION("HoverSet") + { + setHoverRenderer(true); + TEST_DRAW("ToggleButton_Hover_TextureHoverSet.png") + + SECTION("DownSet") + { + setDownRenderer(true); + TEST_DRAW("ToggleButton_Hover_TextureDownSet.png") + } + } + } + + SECTION("DownState") + { + button->mouseMoved(mousePos); + button->leftMousePressed(mousePos); + button->leftMouseReleased(mousePos); + button->mouseMoved({0, 0}); + + TEST_DRAW("ToggleButton_Down_TextureNormalSet.png") + + SECTION("HoverSet") + { + setHoverRenderer(true); + TEST_DRAW("ToggleButton_Down_TextureHoverSet.png") + + SECTION("DownSet") + { + setDownRenderer(true); + TEST_DRAW("ToggleButton_Down_TextureDownSet.png") + } + } + } + + SECTION("DownHoverState") + { + button->mouseMoved(mousePos); + button->leftMousePressed(mousePos); + button->leftMouseReleased(mousePos); + + TEST_DRAW("ToggleButton_DownHover_TextureNormalSet.png") + + SECTION("HoverSet") + { + setHoverRenderer(true); + TEST_DRAW("ToggleButton_DownHover_TextureHoverSet.png") + + SECTION("DownSet") + { + setDownRenderer(true); + TEST_DRAW("ToggleButton_DownHover_TextureDownSet.png") + } + } + } + + SECTION("DisabledState") + { + button->setEnabled(false); + + TEST_DRAW("ToggleButton_Disabled_TextureNormalSet.png") + + SECTION("DisabledSet") + { + setDisabledRenderer(true); + TEST_DRAW("ToggleButton_Disabled_TextureDisabledSet.png") + } + } + } + } } diff --git a/tests/expected/ToggleButton_Disabled_DisabledSet.png b/tests/expected/ToggleButton_Disabled_DisabledSet.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e32efb54a94868418464e0002c8ad4a7387b41 GIT binary patch literal 1382 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 literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Disabled_TextureNormalSet.png b/tests/expected/ToggleButton_Disabled_TextureNormalSet.png new file mode 100644 index 0000000000000000000000000000000000000000..1d4254762ead225c9f45d269719094f429114c2f GIT binary patch literal 3400 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 X_G65fR9Lx`kYFKO!vbr!(D?rccSTs5 literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_DownHover_DownSet.png b/tests/expected/ToggleButton_DownHover_DownSet.png new file mode 100644 index 0000000000000000000000000000000000000000..645a207b6f1adc8249b3815d26f785ec96dd94e9 GIT binary patch literal 1488 zcmV;>1uy!EP)PHGzSm!=|2+N$jppP*6aqV#))D2J z(HuOQgApMR85ud6iw7_SmM>pUvhAZec$g>I(Fh*q5zNf%$l5@RA65QqGU)2+k`)}U zpTB0a@HE4MiHjLh%aXxn4n!=|G=e|9|HPo7r%qLWfpYGz zpT8M0s?vagiKv_P&Gj(s0p+?yla?~D0&`?aNg@L`FE>HAu(Gi-M5Ki=FfuVRsA{V) z+`svN;ljyF3}Gq33_8ZzVEtS?oD4I7iR|vRdki`z5U~SW4}nXm#G*I`4o(iRI%N$d zhRIFS7!GYe%n*?tHXylp$aRxI#j?7dDoW&H3pr5BeOA|820lSPhNPl+q9PY$4yXkW zid>kPBES*?#6~WTg~f!y(jS2d7^d#_wL1)|z;*>28ymyFzyH7(Y#y-cczEX#!$1L= z=3r3ZegM{8)b!lfFW(48F37$Iw;uxQ=kE-jA?^&MR1u(d0kV5xT{u{-1j)g~{`~$8 zSNiqyHwIAg4#MdEWn^F&o)N4BOud&+Ut#zK>=se09_HrZV)*p_Gr<5eu{CD+@$Co0 z8ekU<)X>v1)Szh~b8>MoD5xm_>u4*2_B2heqg#a2w1~EHwq*GG=Pz)8V<*G^|ClR- zKnSru(%@F2;j5BMV))FtzY^kOixGhtXYXAJ|WKTgX~1r_11p1xublNMu; z0`~dQ@jzu^nikQ*VnPf-2?5}Nj<+x0Fc?`IG6;zXGW-H|TOQqg%y9b9S#Zl#5t#Cc z4FD$}2ZmdhZ!^s9n#W-8Wy=7v-kipEs3|x(T`|_RP*`sF+OhEfV@-T}VvYHv}fL%W)Uk94UJIzP3KmjTy zDavqU_c4Zjn-4O4{`i@J71+fArF2jPnb;VShES4L$D(2oZgcCOh4uK#)XxEZ8mC4mucielA2 zU}=V}M59G)6TPYuG~quwUC1z?0C~EQ*a?4{GnvQUX z!03GNFi#yMPHGzSm!={}zV-9L>ptCT+ z(HuOQgApO%$FONM7Y|?vY+~>u+4j*KJeq?^3jvOwObqS^#TYEl3o=N(WM|;`$;|Mc z|2M-8r7sM7ZQd|k()a{7a80HOn7!8XIm2?lrv$}`aS1Ln2Wvdi+Py5)^ti-pc7~eC zstgjZIZ%uhe$U2WeO3sJ=Y>6D*yi|Zw5X?32v9A8*?u!IR8ECN?mwpg47+S!Gn~`; z$ncZ>A226#Ft{ETVNiO+&A{`Ou$5J1*TLR@EAV?XAy60sREuDjBO(maFCjU1Qo?

J3BKRw)LX(?Sf~-UCu8cbjKY*nQAvb`k6qp*( zckB%DYh)P$cStbsePM-b)xE*TP&!kYp``j6T#Ol*2tn19)@_74`MxkQI35>a(7eOL zP*!=J;Tz{)26_RiMX>C1POzJAsC>rjc5*!j%DJ$}J*)eHVK=bB2C53w@9}_(e{@S( z{xCA+EL3E$KPe2BIs(k)lYu46UuNPa&;9{Z$1?w?43DJ0Gh{7RU=aAk3Xa@63SSvk z`aEMWy~NMpc1R4I!?kYnFkDmp0=CC%zZe57a`ysDt)s^88Px7`GlXxJ0=G^4cT1o& zC}|zhREuC(&iqQF)Cnr?K!Nc@;yXiU#$5(bHO;A_WJ~PaU zd;~TR#Fl%;$zTO+XqulF7@83b%9)@B6gQbgFrkzTtKUI^1&dsm9&AM}EOIxwykJ-q z{DkC;@RHCWfDE|4_^X z)s;$*xEMeUq~=}?hWetL7_D7e1oCsypWraK3>?r|5`bB9fZU2~Z^-mBG>vPj<={QQ zM$a>GNb!}oK#?J3g&c#?6@CU#1H}2L2t!-921Dc~X$D3z7YH3Pe+v!>nHQW4Wiyl+ zj4$&u7+mECH(sV!>tY1bMa@qPUFmlj{xbh#5CIOfH1(-7h`*tI@pusGPEZRQ)N+4A_+shDLII;Z24YqO_a(+~WL_!QeU{ z11N&Ag~1`Sw7zGd{9yPR`3@CsGHIN z3|w-DDM8(vmfTyw!Gb3Ypq4Z!XM)VXFZ-2YaUkwS6sR@ak$IQl56eFWSR_ln;$WZ^ z0J{@7kg_P~3ByC_Zw$X#{{j2mf5C3vW%r6|8o|Rnf=Oycle1)Gw4fZ=5E#wDqeb*+9vsRcFq(shdHNmQT{v1q vj~3CRMf7MI7_=cUS_h97(WBXOD2D(5z(@&IQxFB!00000NkvXXu0mjfnRV6| literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_DownHover_NormalSet.png b/tests/expected/ToggleButton_DownHover_NormalSet.png new file mode 100644 index 0000000000000000000000000000000000000000..2bfa5342c3b29f3a61708d60f9e9051e82c7f71c GIT binary patch literal 1801 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 X_G65fR9Lx`kYFKO!vbr!(D?rccSTs5 literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Down_DownSet.png b/tests/expected/ToggleButton_Down_DownSet.png new file mode 100644 index 0000000000000000000000000000000000000000..645a207b6f1adc8249b3815d26f785ec96dd94e9 GIT binary patch literal 1488 zcmV;>1uy!EP)PHGzSm!=|2+N$jppP*6aqV#))D2J z(HuOQgApMR85ud6iw7_SmM>pUvhAZec$g>I(Fh*q5zNf%$l5@RA65QqGU)2+k`)}U zpTB0a@HE4MiHjLh%aXxn4n!=|G=e|9|HPo7r%qLWfpYGz zpT8M0s?vagiKv_P&Gj(s0p+?yla?~D0&`?aNg@L`FE>HAu(Gi-M5Ki=FfuVRsA{V) z+`svN;ljyF3}Gq33_8ZzVEtS?oD4I7iR|vRdki`z5U~SW4}nXm#G*I`4o(iRI%N$d zhRIFS7!GYe%n*?tHXylp$aRxI#j?7dDoW&H3pr5BeOA|820lSPhNPl+q9PY$4yXkW zid>kPBES*?#6~WTg~f!y(jS2d7^d#_wL1)|z;*>28ymyFzyH7(Y#y-cczEX#!$1L= z=3r3ZegM{8)b!lfFW(48F37$Iw;uxQ=kE-jA?^&MR1u(d0kV5xT{u{-1j)g~{`~$8 zSNiqyHwIAg4#MdEWn^F&o)N4BOud&+Ut#zK>=se09_HrZV)*p_Gr<5eu{CD+@$Co0 z8ekU<)X>v1)Szh~b8>MoD5xm_>u4*2_B2heqg#a2w1~EHwq*GG=Pz)8V<*G^|ClR- zKnSru(%@F2;j5BMV))FtzY^kOixGhtXYXAJ|WKTgX~1r_11p1xublNMu; z0`~dQ@jzu^nikQ*VnPf-2?5}Nj<+x0Fc?`IG6;zXGW-H|TOQqg%y9b9S#Zl#5t#Cc z4FD$}2ZmdhZ!^s9n#W-8Wy=7v-kipEs3|x(T`|_RP*`sF+OhEfV@-T}VvYHv}fL%W)Uk94UJIzP3KmjTy zDavqU_c4Zjn-4O4{`i@J71+fArF2jPnb;VShES4L$D(2oZgcCOh4uK#)XxEZ8mC4mucielA2 zU}=V}M59G)6TPYuG~quwUC1z?0C~EQ*a?4{GnvQUX z!03GNFi#yMPHGzSm!={}zV-9L>ptCT+ z(HuOQgApO%$FONM7Y|?vY+~>u+4j*KJeq?^3jvOwObqS^#TYEl3o=N(WM|;`$;|Mc z|2M-8r7sM7ZQd|k()a{7a80HOn7!8XIm2?lrv$}`aS1Ln2Wvdi+Py5)^ti-pc7~eC zstgjZIZ%uhe$U2WeO3sJ=Y>6D*yi|Zw5X?32v9A8*?u!IR8ECN?mwpg47+S!Gn~`; z$ncZ>A226#Ft{ETVNiO+&A{`Ou$5J1*TLR@EAV?XAy60sREuDjBO(maFCjU1Qo?

J3BKRw)LX(?Sf~-UCu8cbjKY*nQAvb`k6qp*( zckB%DYh)P$cStbsePM-b)xE*TP&!kYp``j6T#Ol*2tn19)@_74`MxkQI35>a(7eOL zP*!=J;Tz{)26_RiMX>C1POzJAsC>rjc5*!j%DJ$}J*)eHVK=bB2C53w@9}_(e{@S( z{xCA+EL3E$KPe2BIs(k)lYu46UuNPa&;9{Z$1?w?43DJ0Gh{7RU=aAk3Xa@63SSvk z`aEMWy~NMpc1R4I!?kYnFkDmp0=CC%zZe57a`ysDt)s^88Px7`GlXxJ0=G^4cT1o& zC}|zhREuC(&iqQF)Cnr?K!Nc@;yXiU#$5(bHO;A_WJ~PaU zd;~TR#Fl%;$zTO+XqulF7@83b%9)@B6gQbgFrkzTtKUI^1&dsm9&AM}EOIxwykJ-q z{DkC;@RHCWfDE|4_^X z)s;$*xEMeUq~=}?hWetL7_D7e1oCsypWraK3>?r|5`bB9fZU2~Z^-mBG>vPj<={QQ zM$a>GNb!}oK#?J3g&c#?6@CU#1H}2L2t!-921Dc~X$D3z7YH3Pe+v!>nHQW4Wiyl+ zj4$&u7+mECH(sV!>tY1bMa@qPUFmlj{xbh#5CIOfH1(-7h`*tI@pusGPEZRQ)N+4A_+shDLII;Z24YqO_a(+~WL_!QeU{ z11N&Ag~1`Sw7zGd{9yPR`3@CsGHIN z3|w-DDM8(vmfTyw!Gb3Ypq4Z!XM)VXFZ-2YaUkwS6sR@ak$IQl56eFWSR_ln;$WZ^ z0J{@7kg_P~3ByC_Zw$X#{{j2mf5C3vW%r6|8o|Rnf=Oycle1)Gw4fZ=5E#wDqeb*+9vsRcFq(shdHNmQT{v1q vj~3CRMf7MI7_=cUS_h97(WBXOD2D(5z(@&IQxFB!00000NkvXXu0mjfnRV6| literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Down_NormalSet.png b/tests/expected/ToggleButton_Down_NormalSet.png new file mode 100644 index 0000000000000000000000000000000000000000..2bfa5342c3b29f3a61708d60f9e9051e82c7f71c GIT binary patch literal 1801 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 X_G65fR9Lx`kYFKO!vbr!(D?rccSTs5 literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Hover_DownSet.png b/tests/expected/ToggleButton_Hover_DownSet.png new file mode 100644 index 0000000000000000000000000000000000000000..4eff7fc3e549bc9b8c2908e468ea6cb4f892808b GIT binary patch literal 1597 zcmV-D2EzG?P)PHGzSm!={}zV-9L>ptCT+ z(HuOQgApO%$FONM7Y|?vY+~>u+4j*KJeq?^3jvOwObqS^#TYEl3o=N(WM|;`$;|Mc z|2M-8r7sM7ZQd|k()a{7a80HOn7!8XIm2?lrv$}`aS1Ln2Wvdi+Py5)^ti-pc7~eC zstgjZIZ%uhe$U2WeO3sJ=Y>6D*yi|Zw5X?32v9A8*?u!IR8ECN?mwpg47+S!Gn~`; z$ncZ>A226#Ft{ETVNiO+&A{`Ou$5J1*TLR@EAV?XAy60sREuDjBO(maFCjU1Qo?

J3BKRw)LX(?Sf~-UCu8cbjKY*nQAvb`k6qp*( zckB%DYh)P$cStbsePM-b)xE*TP&!kYp``j6T#Ol*2tn19)@_74`MxkQI35>a(7eOL zP*!=J;Tz{)26_RiMX>C1POzJAsC>rjc5*!j%DJ$}J*)eHVK=bB2C53w@9}_(e{@S( z{xCA+EL3E$KPe2BIs(k)lYu46UuNPa&;9{Z$1?w?43DJ0Gh{7RU=aAk3Xa@63SSvk z`aEMWy~NMpc1R4I!?kYnFkDmp0=CC%zZe57a`ysDt)s^88Px7`GlXxJ0=G^4cT1o& zC}|zhREuC(&iqQF)Cnr?K!Nc@;yXiU#$5(bHO;A_WJ~PaU zd;~TR#Fl%;$zTO+XqulF7@83b%9)@B6gQbgFrkzTtKUI^1&dsm9&AM}EOIxwykJ-q z{DkC;@RHCWfDE|4_^X z)s;$*xEMeUq~=}?hWetL7_D7e1oCsypWraK3>?r|5`bB9fZU2~Z^-mBG>vPj<={QQ zM$a>GNb!}oK#?J3g&c#?6@CU#1H}2L2t!-921Dc~X$D3z7YH3Pe+v!>nHQW4Wiyl+ zj4$&u7+mECH(sV!>tY1bMa@qPUFmlj{xbh#5CIOfH1(-7h`*tI@pusGPEZRQ)N+4A_+shDLII;Z24YqO_a(+~WL_!QeU{ z11N&Ag~1`Sw7zGd{9yPR`3@CsGHIN z3|w-DDM8(vmfTyw!Gb3Ypq4Z!XM)VXFZ-2YaUkwS6sR@ak$IQl56eFWSR_ln;$WZ^ z0J{@7kg_P~3ByC_Zw$X#{{j2mf5C3vW%r6|8o|Rnf=Oycle1)Gw4fZ=5E#wDqeb*+9vsRcFq(shdHNmQT{v1q vj~3CRMf7MI7_=cUS_h97(WBXOD2D(5z(@&IQxFB!00000NkvXXu0mjfnRV6| literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Hover_HoverSet.png b/tests/expected/ToggleButton_Hover_HoverSet.png new file mode 100644 index 0000000000000000000000000000000000000000..4eff7fc3e549bc9b8c2908e468ea6cb4f892808b GIT binary patch literal 1597 zcmV-D2EzG?P)PHGzSm!={}zV-9L>ptCT+ z(HuOQgApO%$FONM7Y|?vY+~>u+4j*KJeq?^3jvOwObqS^#TYEl3o=N(WM|;`$;|Mc z|2M-8r7sM7ZQd|k()a{7a80HOn7!8XIm2?lrv$}`aS1Ln2Wvdi+Py5)^ti-pc7~eC zstgjZIZ%uhe$U2WeO3sJ=Y>6D*yi|Zw5X?32v9A8*?u!IR8ECN?mwpg47+S!Gn~`; z$ncZ>A226#Ft{ETVNiO+&A{`Ou$5J1*TLR@EAV?XAy60sREuDjBO(maFCjU1Qo?

J3BKRw)LX(?Sf~-UCu8cbjKY*nQAvb`k6qp*( zckB%DYh)P$cStbsePM-b)xE*TP&!kYp``j6T#Ol*2tn19)@_74`MxkQI35>a(7eOL zP*!=J;Tz{)26_RiMX>C1POzJAsC>rjc5*!j%DJ$}J*)eHVK=bB2C53w@9}_(e{@S( z{xCA+EL3E$KPe2BIs(k)lYu46UuNPa&;9{Z$1?w?43DJ0Gh{7RU=aAk3Xa@63SSvk z`aEMWy~NMpc1R4I!?kYnFkDmp0=CC%zZe57a`ysDt)s^88Px7`GlXxJ0=G^4cT1o& zC}|zhREuC(&iqQF)Cnr?K!Nc@;yXiU#$5(bHO;A_WJ~PaU zd;~TR#Fl%;$zTO+XqulF7@83b%9)@B6gQbgFrkzTtKUI^1&dsm9&AM}EOIxwykJ-q z{DkC;@RHCWfDE|4_^X z)s;$*xEMeUq~=}?hWetL7_D7e1oCsypWraK3>?r|5`bB9fZU2~Z^-mBG>vPj<={QQ zM$a>GNb!}oK#?J3g&c#?6@CU#1H}2L2t!-921Dc~X$D3z7YH3Pe+v!>nHQW4Wiyl+ zj4$&u7+mECH(sV!>tY1bMa@qPUFmlj{xbh#5CIOfH1(-7h`*tI@pusGPEZRQ)N+4A_+shDLII;Z24YqO_a(+~WL_!QeU{ z11N&Ag~1`Sw7zGd{9yPR`3@CsGHIN z3|w-DDM8(vmfTyw!Gb3Ypq4Z!XM)VXFZ-2YaUkwS6sR@ak$IQl56eFWSR_ln;$WZ^ z0J{@7kg_P~3ByC_Zw$X#{{j2mf5C3vW%r6|8o|Rnf=Oycle1)Gw4fZ=5E#wDqeb*+9vsRcFq(shdHNmQT{v1q vj~3CRMf7MI7_=cUS_h97(WBXOD2D(5z(@&IQxFB!00000NkvXXu0mjfnRV6| literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Hover_NormalSet.png b/tests/expected/ToggleButton_Hover_NormalSet.png new file mode 100644 index 0000000000000000000000000000000000000000..2bfa5342c3b29f3a61708d60f9e9051e82c7f71c GIT binary patch literal 1801 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 X_G65fR9Lx`kYFKO!vbr!(D?rccSTs5 literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Normal_DownSet.png b/tests/expected/ToggleButton_Normal_DownSet.png new file mode 100644 index 0000000000000000000000000000000000000000..2bfa5342c3b29f3a61708d60f9e9051e82c7f71c GIT binary patch literal 1801 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 X_G65fR9Lx`kYFKO!vbr!(D?rccSTs5 literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Normal_TextureHoverSet.png b/tests/expected/ToggleButton_Normal_TextureHoverSet.png new file mode 100644 index 0000000000000000000000000000000000000000..1d4254762ead225c9f45d269719094f429114c2f GIT binary patch literal 3400 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 X_G65fR9Lx`kYFKO!vbr!(D?rccSTs5 literal 0 HcmV?d00001 diff --git a/tests/expected/ToggleButton_Normal_TextureNormalSet.png b/tests/expected/ToggleButton_Normal_TextureNormalSet.png new file mode 100644 index 0000000000000000000000000000000000000000..1d4254762ead225c9f45d269719094f429114c2f GIT binary patch literal 3400 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 X_G65fR9Lx`kYFKO!vbr!(D?rccSTs5 literal 0 HcmV?d00001 From cef8b970f14dc61fd600eca411280ad418ca8bd9 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Mon, 18 Sep 2023 19:25:01 +0200 Subject: [PATCH 009/363] Try getting different OpenGL versions on CI to test different code paths --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52b3495b5..1c64a0480 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -204,12 +204,12 @@ jobs: xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SFML_GRAPHICS xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SFML_OPENGL3 xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_RENDERER - xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_OPENGL3 - xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_GLES2 - xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_TTF_OPENGL3 - xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_TTF_GLES2 - xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=GLFW_OPENGL3 - xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=GLFW_GLES2 + MESA_GL_VERSION_OVERRIDE=3.3 xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_OPENGL3 + MESA_GLES_VERSION_OVERRIDE=3.2 xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_GLES2 + MESA_GL_VERSION_OVERRIDE=4.1 xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_TTF_OPENGL3 + MESA_GLES_VERSION_OVERRIDE=3.0 xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=SDL_TTF_GLES2 + MESA_GL_VERSION_OVERRIDE=4.5 xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=GLFW_OPENGL3 + MESA_GLES_VERSION_OVERRIDE=2.0 xvfb-run --server-args="-screen 0, 1280x720x24" -a ./tests --backend=GLFW_GLES2 - name: Upload coverage reports to Codecov working-directory: TGUI From d99aff0aeb1a47bcc9bfe4b18233dbd1b31f34b0 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Mon, 18 Sep 2023 19:33:27 +0200 Subject: [PATCH 010/363] Some tests were failing when running them on Windows --- tests/Filesystem.cpp | 9 ++++++++- tests/Tests.cpp | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/Filesystem.cpp b/tests/Filesystem.cpp index 3257a8e0e..fc324ee51 100644 --- a/tests/Filesystem.cpp +++ b/tests/Filesystem.cpp @@ -48,12 +48,19 @@ TEST_CASE("[Filesystem]") SECTION("Absolute / relative") { +#ifdef TGUI_SYSTEM_WINDOWS + REQUIRE(tgui::Filesystem::Path("C:/a/b/").isAbsolute()); + REQUIRE(!tgui::Filesystem::Path("C:/a/b/").isRelative()); + + REQUIRE(tgui::Filesystem::Path("D:/a/b").isAbsolute()); + REQUIRE(!tgui::Filesystem::Path("D:/a/b").isRelative()); +#else REQUIRE(tgui::Filesystem::Path("/a/b/").isAbsolute()); REQUIRE(!tgui::Filesystem::Path("/a/b/").isRelative()); REQUIRE(tgui::Filesystem::Path("/a/b").isAbsolute()); REQUIRE(!tgui::Filesystem::Path("/a/b").isRelative()); - +#endif REQUIRE(!tgui::Filesystem::Path("a/b/").isAbsolute()); REQUIRE(tgui::Filesystem::Path("a/b/").isRelative()); diff --git a/tests/Tests.cpp b/tests/Tests.cpp index 2e2a0ef3a..010fc64a0 100644 --- a/tests/Tests.cpp +++ b/tests/Tests.cpp @@ -35,7 +35,8 @@ tgui::String getClipboardContents() // Wait a moment before accessing the clipboard. // The data wasn't always there yet when requesting it immediately after changing it when using the SDL backend on Windows. // The problem might be with setting instead of getting though: setting two times quickly resulted in access denied error. - Sleep(1); + // There were still occational failures when only waiting 1ms. + Sleep(5); #endif return tgui::getBackend()->getClipboard(); From 0282e78cdf49321212ffe6a64de005ca3c8458a7 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Mon, 18 Sep 2023 19:34:53 +0200 Subject: [PATCH 011/363] Test drawing a Sprite on a CanvasSFML --- .../Renderer/SFML-Graphics/CanvasSFML.cpp | 18 ++++++++++++------ tests/Widgets/Canvas.cpp | 5 +++++ tests/expected/Canvas.png | Bin 4831 -> 4952 bytes 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp b/src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp index 99c0f6412..f0c649803 100644 --- a/src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp +++ b/src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp @@ -197,23 +197,29 @@ namespace tgui if (!sprite.getTexture().getData()) return; - const std::vector& vertices = sprite.getVertices(); - const std::vector& indices = sprite.getIndices(); - std::vector triangleVertices(indices.size()); - for (std::size_t i = 0; i < indices.size(); ++i) - triangleVertices[i] = vertices[indices[i]]; - sf::RenderStates statesSFML; const std::array& transformMatrix = states.transform.getMatrix(); statesSFML.transform = sf::Transform( transformMatrix[0], transformMatrix[4], transformMatrix[12], transformMatrix[1], transformMatrix[5], transformMatrix[13], transformMatrix[3], transformMatrix[7], transformMatrix[15]); + statesSFML.transform.translate({sprite.getPosition()}); TGUI_ASSERT(std::dynamic_pointer_cast(sprite.getTexture().getData()->backendTexture), "CanvasSFML::draw requires sprite to have a backend texture of type BackendTextureSFML"); statesSFML.texture = &std::static_pointer_cast(sprite.getTexture().getData()->backendTexture)->getInternalTexture(); + const sf::Vector2u textureSize{statesSFML.texture->getSize()}; + const std::vector& vertices = sprite.getVertices(); + const std::vector& indices = sprite.getIndices(); + std::vector triangleVertices(indices.size()); + for (std::size_t i = 0; i < indices.size(); ++i) + { + triangleVertices[i] = vertices[indices[i]]; + triangleVertices[i].texCoords.x *= textureSize.x; + triangleVertices[i].texCoords.y *= textureSize.y; + } + static_assert(sizeof(Vertex) == sizeof(sf::Vertex), "Size of sf::Vertex has to match with tgui::Vertex for optimization to work"); const sf::Vertex* sfmlVertices = reinterpret_cast(triangleVertices.data()); m_renderTexture.draw(sfmlVertices, indices.size(), sf::PrimitiveType::Triangles, statesSFML); diff --git a/tests/Widgets/Canvas.cpp b/tests/Widgets/Canvas.cpp index 0329407ab..fad71f9b1 100644 --- a/tests/Widgets/Canvas.cpp +++ b/tests/Widgets/Canvas.cpp @@ -127,6 +127,10 @@ TEST_CASE("[CanvasSFML]") sprite.setScale({150.f / texture.getSize().x, 100.f / texture.getSize().y}); sprite.setPosition({15, 20}); + tgui::Sprite sprite2("resources/Texture6.png"); + sprite2.setSize({25, 25}); + sprite2.setPosition({40, 35}); + std::vector vertices = { {{80, 90}, tgui::Color::Red}, {{80, 115}, tgui::Color::Red}, @@ -136,6 +140,7 @@ TEST_CASE("[CanvasSFML]") canvas->clear(tgui::Color::Yellow); canvas->draw(sprite); + canvas->draw(sprite2); canvas->draw(vertices.data(), vertices.size(), sf::PrimitiveType::TriangleStrip); canvas->display(); diff --git a/tests/expected/Canvas.png b/tests/expected/Canvas.png index 29e90352c9da56a023c3e49ae6aa39ca04601ee5..6936eeb7627875cce65ee288f3509050abdefc19 100644 GIT binary patch delta 3850 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 delta 3673 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 From f10e5d324fac1912d87ec2601a6de7228e095a11 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Mon, 18 Sep 2023 19:35:37 +0200 Subject: [PATCH 012/363] Test using an sf::Shader on a Texture --- .../SFML-Graphics/BackendRenderTargetSFML.cpp | 7 +-- tests/Texture.cpp | 46 +++++++++++++++++- tests/expected/Texture_SFML_Shader.png | Bin 0 -> 463 bytes .../expected/Texture_SFML_Shader_Rotation.png | Bin 0 -> 460 bytes 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/expected/Texture_SFML_Shader.png create mode 100644 tests/expected/Texture_SFML_Shader_Rotation.png diff --git a/src/Backend/Renderer/SFML-Graphics/BackendRenderTargetSFML.cpp b/src/Backend/Renderer/SFML-Graphics/BackendRenderTargetSFML.cpp index e176c67e3..dd0267f5a 100644 --- a/src/Backend/Renderer/SFML-Graphics/BackendRenderTargetSFML.cpp +++ b/src/Backend/Renderer/SFML-Graphics/BackendRenderTargetSFML.cpp @@ -102,15 +102,16 @@ namespace tgui return BackendRenderTarget::drawSprite(states, sprite); RenderStates transformedStates = states; - if (sprite.getRotation() != 0) + if (sprite.getRotation() == 0) + transformedStates.transform.translate(sprite.getPosition()); + else { // A rotation can cause the image to be shifted, so we move it upfront so that it ends at the correct location transformedStates.transform.translate(-Transform().rotate(sprite.getRotation()).transformRect({{}, sprite.getSize()}).getPosition()); + transformedStates.transform.translate(sprite.getPosition()); transformedStates.transform.rotate(sprite.getRotation()); } - transformedStates.transform.translate(sprite.getPosition()); - const FloatRect& visibleRect = sprite.getVisibleRect(); const bool clippingRequired = (visibleRect != FloatRect{}); if (clippingRequired) diff --git a/tests/Texture.cpp b/tests/Texture.cpp index 5faa23087..81275864a 100644 --- a/tests/Texture.cpp +++ b/tests/Texture.cpp @@ -41,6 +41,25 @@ #endif #endif +#if TGUI_HAS_BACKEND_SFML_GRAPHICS +class CustomWidgetWithSFMLShader : public tgui::ClickableWidget +{ +public: + tgui::Sprite sprite; + + static std::shared_ptr create() + { + return std::make_shared(); + } + + void draw(tgui::BackendRenderTarget& target, tgui::RenderStates states) const override + { + states.transform.translate({5, -10}); + target.drawSprite(states, sprite); + } +}; +#endif + TEST_CASE("[Texture]") { SECTION("Loading") @@ -233,7 +252,7 @@ TEST_CASE("[Texture]") { SECTION("Shader") { - tgui::Texture texture{"resources/image.png"}; + tgui::Texture texture{"resources/Texture6.png"}; REQUIRE(!texture.getShader()); sf::Shader shader; @@ -242,6 +261,31 @@ TEST_CASE("[Texture]") texture.setShader(nullptr); REQUIRE(!texture.getShader()); + + SECTION("Draw") + { + (void)shader.loadFromMemory(R"( + void main() { + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + gl_FrontColor = gl_Color; + })", R"( + uniform sampler2D texture; + void main() { + gl_FragColor = gl_Color * texture2D(texture, gl_TexCoord[0].xy) * vec4(0.7, 1.0, 0.0, 1.0); + })"); + texture.setShader(&shader); + + auto widget = CustomWidgetWithSFMLShader::create(); + widget->sprite.setTexture(texture); + widget->sprite.setPosition({5, 20}); + TEST_DRAW_INIT(70, 70, widget) + + TEST_DRAW("Texture_SFML_Shader.png") + + widget->sprite.setRotation(90); + TEST_DRAW("Texture_SFML_Shader_Rotation.png") + } } } #endif diff --git a/tests/expected/Texture_SFML_Shader.png b/tests/expected/Texture_SFML_Shader.png new file mode 100644 index 0000000000000000000000000000000000000000..1b52c8ebe743a18d61d0bf4c7a34976d2ded822d GIT binary patch literal 463 zcmeAS@N?(olHy`uVBq!ia0vp^ZXnFT1|$ph9fLG)cQ{ zGt8}f8YCGTovIG>OmI-tO>hfX5U_lgTb?OzLie;4$D}zI-@cOllW(!Z!niKUv__}7 z4U%n*PL&6EE;}sLNoXrvFhSnnuuZ@T?iqpt8cW#EaBzxTVoGCS<@y@E@9>J-23F6y z7wa2ZJ@ek&ASwI#sL+UO-wJxXR$G_sHLJU*~+ zPoudHbGw!9{X_|iE3bukHXm4+7oRD?%J-4cm?vuQk}dx{ybYB#3OvqxDXSEm&`x%9 zcrYRU2jAxd3)h569cy&1EVTt0@OB2sfb0jnAOlX9LkwtYGj-HWxfFEf#SHylUJC1=W0mdHE`1}haYw0dSOWwAJUpT(sE zK)qL)*-{>WbZZy89k0opG<5>6tn6o5$(vavXQpdKW`Z>HXE)XO$E`JC3OWbW;k)W} z;+!Io;#&u#W1m|WWof_SZEHT1Be8VO#m%xA_c~J~;u<;=MG|-$FE&V?^;(kJdr`!A qR+3vVPw1-27uT4~n(E>0!Qi&P{$8in_dCE?V(@hJb6Mw<&;$Uw2gEo4 literal 0 HcmV?d00001 From 50cb4139cc8426f4fe674be49aa9d5f8da246e9d Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Mon, 18 Sep 2023 19:40:01 +0200 Subject: [PATCH 013/363] Also let CI run tests on Windows --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c64a0480..0b9cc192a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -577,7 +577,7 @@ jobs: -DTGUI_WARNINGS_AS_ERRORS=ON -DTGUI_BUILD_EXAMPLES=ON -DTGUI_BUILD_GUI_BUILDER=ON - -DTGUI_BUILD_TESTS=ON + -DTGUI_BUILD_TESTS=OFF -DTGUI_BACKEND=SFML_GRAPHICS cmake --build TGUI-build-ClangCL --config Release @@ -598,6 +598,10 @@ jobs: cmake --build TGUI-build-LLVM-Clang --config Debug + - name: Run tests + working-directory: TGUI-build-LLVM-Clang/tests/Release + run: tests.exe + #---------------------------------------- windows-static-mt: From e3de6a178f300d909b6ec065169c592f2c3faad1 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Mon, 18 Sep 2023 19:50:56 +0200 Subject: [PATCH 014/363] Some compilers didn't like the implicit conversion in recent CanvasSFML change --- src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp b/src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp index f0c649803..1f683c8d5 100644 --- a/src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp +++ b/src/Backend/Renderer/SFML-Graphics/CanvasSFML.cpp @@ -203,7 +203,7 @@ namespace tgui transformMatrix[0], transformMatrix[4], transformMatrix[12], transformMatrix[1], transformMatrix[5], transformMatrix[13], transformMatrix[3], transformMatrix[7], transformMatrix[15]); - statesSFML.transform.translate({sprite.getPosition()}); + statesSFML.transform.translate({sprite.getPosition().x, sprite.getPosition().y}); TGUI_ASSERT(std::dynamic_pointer_cast(sprite.getTexture().getData()->backendTexture), "CanvasSFML::draw requires sprite to have a backend texture of type BackendTextureSFML"); From ef7de6e180791f04b7dead0da173cf2865ff582a Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Tue, 19 Sep 2023 19:24:53 +0200 Subject: [PATCH 015/363] Added several more test cases --- .github/workflows/ci.yml | 2 +- include/TGUI/Loading/WidgetFactory.hpp | 6 ++- src/Loading/WidgetFactory.cpp | 13 ++++-- src/Widgets/ColorPicker.cpp | 9 ++++ src/Widgets/FileDialog.cpp | 9 +++- src/Widgets/MessageBox.cpp | 3 ++ src/Widgets/SpinControl.cpp | 3 ++ src/Widgets/TabContainer.cpp | 4 +- tests/Tests.hpp | 14 ++++++ tests/Widget.cpp | 12 ++++++ tests/Widgets/ListView.cpp | 59 ++++++++++++++++++++++++++ 11 files changed, 124 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b9cc192a..1926b724c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -599,7 +599,7 @@ jobs: cmake --build TGUI-build-LLVM-Clang --config Debug - name: Run tests - working-directory: TGUI-build-LLVM-Clang/tests/Release + working-directory: TGUI-build-LLVM-Clang/tests/Debug run: tests.exe #---------------------------------------- diff --git a/include/TGUI/Loading/WidgetFactory.hpp b/include/TGUI/Loading/WidgetFactory.hpp index 1788cab02..1eccf6e0e 100644 --- a/include/TGUI/Loading/WidgetFactory.hpp +++ b/include/TGUI/Loading/WidgetFactory.hpp @@ -50,7 +50,7 @@ TGUI_MODULE_EXPORT namespace tgui /// @param type Type of the widget /// @param constructor Function used to construct the widget (all TGUI widgets use std::make_shared) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - static void setConstructFunction(const String& type, const std::function& constructor); + static void setConstructFunction(const String& type, std::function constructor); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -59,8 +59,10 @@ TGUI_MODULE_EXPORT namespace tgui /// @param type Type of the widget /// /// @return Function used to construct the widget (all TGUI widgets use std::make_shared) + /// + /// @warning A nullptr is returned if called with a type that isn't in the list returned by getWidgetTypes() ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - TGUI_NODISCARD static const std::function& getConstructFunction(const String& type); + TGUI_NODISCARD static std::function getConstructFunction(const String& type); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/Loading/WidgetFactory.cpp b/src/Loading/WidgetFactory.cpp index 28400caf4..de245e41d 100644 --- a/src/Loading/WidgetFactory.cpp +++ b/src/Loading/WidgetFactory.cpp @@ -76,16 +76,21 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void WidgetFactory::setConstructFunction(const String& type, const std::function& constructor) + void WidgetFactory::setConstructFunction(const String& type, std::function constructor) { - m_constructFunctions[type] = constructor; + m_constructFunctions[type] = std::move(constructor); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - const std::function& WidgetFactory::getConstructFunction(const String& type) + std::function WidgetFactory::getConstructFunction(const String& type) { - return m_constructFunctions[type]; + // If the type is not found then we will return a nullptr, without altering the list that could be returned by getWidgetTypes() + auto it = m_constructFunctions.find(type); + if (it != m_constructFunctions.end()) + return it->second; + else + return nullptr; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/Widgets/ColorPicker.cpp b/src/Widgets/ColorPicker.cpp index 8b70d13e1..a6d7cb759 100644 --- a/src/Widgets/ColorPicker.cpp +++ b/src/Widgets/ColorPicker.cpp @@ -602,6 +602,15 @@ namespace tgui ChildWindow::load(node, renderers); + if (!get("#TGUI_INTERNAL$ColorPickerRed#") || !get("#TGUI_INTERNAL$ColorPickerGreen#") || !get("#TGUI_INTERNAL$ColorPickerBlue#") + || !get("#TGUI_INTERNAL$ColorPickerAlpha#") || !get("#TGUI_INTERNAL$ColorPickerValue#") || !get("#TGUI_INTERNAL$ColorPickerLast#") + || !get("#TGUI_INTERNAL$ColorPickerCurrent#") || !get