diff --git a/.ecode/project_build.json b/.ecode/project_build.json index e1b483ec7..81c527aa9 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -172,7 +172,7 @@ "eepp-linux": { "build": [ { - "args": "--disable-static-build --with-mold-linker --with-debug-symbols --address-sanitizer gmake", + "args": "--disable-static-build --with-mold-linker --with-debug-symbols --address-sanitizer --with-backend=SDL3 gmake", "command": "premake4", "working_dir": "${project_root}" }, @@ -464,7 +464,7 @@ "eepp-linux-ninja": { "build": [ { - "args": "--disable-static-build --with-debug-symbols ninja", + "args": "--disable-static-build --with-debug-symbols --with-backend=SDL3 ninja", "command": "premake5", "working_dir": "${project_root}" }, diff --git a/include/eepp/window/engine.hpp b/include/eepp/window/engine.hpp index 752ccb166..233c64ec8 100644 --- a/include/eepp/window/engine.hpp +++ b/include/eepp/window/engine.hpp @@ -166,6 +166,13 @@ class EE_API Engine { EE::Window::Window* createSDL2Window( const WindowSettings& Settings, const ContextSettings& Context ); +#ifdef EE_BACKEND_SDL3 + Backend::WindowBackendLibrary* createSDL3Backend( const WindowSettings& Settings ); + + EE::Window::Window* createSDL3Window( const WindowSettings& Settings, + const ContextSettings& Context ); +#endif + EE::Window::Window* createDefaultWindow( const WindowSettings& Settings, const ContextSettings& Context ); diff --git a/include/eepp/window/window.hpp b/include/eepp/window/window.hpp index 52cce8a8c..cbb33143f 100644 --- a/include/eepp/window/window.hpp +++ b/include/eepp/window/window.hpp @@ -38,7 +38,7 @@ enum class WindowFlashOperation { UntilFocused, }; -enum class WindowBackend : Uint32 { SDL2, Default }; +enum class WindowBackend : Uint32 { SDL2, SDL3, Default }; #ifndef EE_SCREEN_KEYBOARD_ENABLED #define EE_SCREEN_KEYBOARD_ENABLED false @@ -89,7 +89,8 @@ class WindowSettings { /** @brief ContextSettings Small class that contains the renderer context information */ class ContextSettings { public: - static constexpr Int32 FrameRateLimitScreenRefreshRate = (std::numeric_limits::max)() - 1; + static constexpr Int32 FrameRateLimitScreenRefreshRate = + ( std::numeric_limits::max )() - 1; inline ContextSettings( bool vsync, Int32 frameRateLimit = FrameRateLimitScreenRefreshRate, Uint32 multisamples = 0, GraphicsLibraryVersion version = GLv_default, diff --git a/premake4.lua b/premake4.lua index a864173f7..1789be88e 100644 --- a/premake4.lua +++ b/premake4.lua @@ -171,7 +171,8 @@ newoption { trigger = "with-backend", description = "Select the backend to use for window and input handling.\n\t\t\tIf no backend is selected or if the selected is not installed the script will search for a backend present in the system, and will use it.", allowed = { - { "SDL2", "SDL2" } + { "SDL2", "SDL2" }, + { "SDL3", "SDL3" }, } } newoption { @@ -802,17 +803,47 @@ function add_sdl2() end end +function add_sdl3() + print("Using SDL3 backend"); + files { "src/eepp/window/backend/SDL3/*.cpp" } + defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_3" } + + if not can_add_static_backend("SDL3") then + if not os.is_real("emscripten") then + table.insert( link_list, get_backend_link_name( "SDL3" ) ) + end + else + insert_static_backend( "SDL3" ) + end +end + function set_apple_config() if is_xcode() or _OPTIONS["use-frameworks"] then linkoptions { "-F /Library/Frameworks" } buildoptions { "-F /Library/Frameworks" } - includedirs { "/Library/Frameworks/SDL2.framework/Headers" } + if table.contains(backends, "SDL2") then + includedirs { "/Library/Frameworks/SDL2.framework/Headers" } + end + if table.contains(backends, "SDL3") then + includedirs { "/Library/Frameworks/SDL3.framework/Headers" } + end end if os.is("macosx") then - defines { "EE_SDL2_FROM_ROOTPATH" } + if table.contains(backends, "SDL2") then + defines { "EE_SDL2_FROM_ROOTPATH" } + end + if table.contains(backends, "SDL3") then + defines { "EE_SDL3_FROM_ROOTPATH" } + end if not is_xcode() and not _OPTIONS["use-frameworks"] then - local sdl2flags = popen("sdl2-config --cflags"):gsub("\n", "") - buildoptions { sdl2flags } + if table.contains(backends, "SDL2") then + local sdl2flags = popen("sdl2-config --cflags"):gsub("\n", "") + buildoptions { sdl2flags } + end + if table.contains(backends, "SDL3") then + local sdl3flags = popen("sdl3-config --cflags"):gsub("\n", "") + buildoptions { sdl3flags } + end end end end @@ -890,9 +921,16 @@ function select_backend() add_sdl2() end + if backend_is("SDL3", "SDL3") then + print("Selected SDL3") + add_sdl3() + end + -- If the selected backend is not present, try to find one present if not backend_selected then - if os_findlib("SDL2", "SDL2") then + if os_findlib("SDL3", "SDL3") then + add_sdl3() + elseif os_findlib("SDL2", "SDL2") then add_sdl2() else print("ERROR: Couldnt find any backend. Forced SDL2.") diff --git a/premake5.lua b/premake5.lua index 124efc44e..631912ef7 100644 --- a/premake5.lua +++ b/premake5.lua @@ -24,6 +24,7 @@ newoption { description = "Select the backend to use for window and input handling.\n\t\t\tIf no backend is selected or if the selected is not installed the script will search for a backend present in the system, and will use it.", allowed = { { "SDL2", "SDL2" }, + { "SDL3", "SDL3" }, } } newoption { @@ -642,17 +643,45 @@ function add_sdl2() table.insert( backends, "SDL2" ) end +function add_sdl3() + print("Using SDL3 backend"); + if not can_add_static_backend("SDL3") then + table.insert( link_list, get_backend_link_name( "SDL3" ) ) + else + print("Using static backend") + insert_static_backend( "SDL3" ) + end + + table.insert( backends, "SDL3" ) +end + function set_apple_config() if is_xcode() or _OPTIONS["use-frameworks"] then linkoptions { "-F /Library/Frameworks" } buildoptions { "-F /Library/Frameworks" } - incdirs { "/Library/Frameworks/SDL2.framework/Headers" } + if table.contains(backends, "SDL2") then + incdirs { "/Library/Frameworks/SDL2.framework/Headers" } + end + if table.contains(backends, "SDL3") then + incdirs { "/Library/Frameworks/SDL3.framework/Headers" } + end end if os.istarget("macosx") then - defines { "EE_SDL2_FROM_ROOTPATH" } + if table.contains(backends, "SDL2") then + defines { "EE_SDL2_FROM_ROOTPATH" } + end + if table.contains(backends, "SDL3") then + defines { "EE_SDL3_FROM_ROOTPATH" } + end if not is_xcode() and not _OPTIONS["use-frameworks"] then - local sdl2flags = popen("sdl2-config --cflags"):gsub("\n", "") - buildoptions { sdl2flags } + if table.contains(backends, "SDL2") then + local sdl2flags = popen("sdl2-config --cflags"):gsub("\n", "") + buildoptions { sdl2flags } + end + if table.contains(backends, "SDL3") then + local sdl3flags = popen("sdl3-config --cflags"):gsub("\n", "") + buildoptions { sdl3flags } + end end end end @@ -718,9 +747,16 @@ function select_backend() add_sdl2() end + if backend_is("SDL3", "SDL3") then + print("Selected SDL3") + add_sdl3() + end + -- If the selected backend is not present, try to find one present if not backend_selected then - if os_findlib("SDL2", "SDL2") then + if os_findlib("SDL3", "SDL3") then + add_sdl3() + elseif os_findlib("SDL2", "SDL2") then add_sdl2() else print("ERROR: Couldnt find any backend. Forced SDL2.") @@ -814,6 +850,11 @@ function build_eepp( build_name ) defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_2" } end + if table.contains( backends, "SDL3" ) then + files { "src/eepp/window/backend/SDL3/*.cpp" } + defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_3" } + end + multiple_insert( link_list, os_links ) links { link_list } @@ -1666,8 +1707,14 @@ workspace "eepp" links { "eterm-static", "languages-syntax-highlighting-static" } incdirs { "src/modules/eterm/include/" } language "C++" - files { "src/tests/unit_tests/*.cpp" } - build_link_configuration( "eepp-unit_tests", true ) + files { "src/tests/unit_tests/*.cpp" } + build_link_configuration( "eepp-unit_tests", true ) + if table.contains(backends, "SDL2") then + defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_2" } + end + if table.contains(backends, "SDL3") then + defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_3" } + end if os.isfile("external_projects.lua") then dofile("external_projects.lua") diff --git a/src/eepp/window/backend/SDL3/backendsdl3.cpp b/src/eepp/window/backend/SDL3/backendsdl3.cpp new file mode 100644 index 000000000..485f098e8 --- /dev/null +++ b/src/eepp/window/backend/SDL3/backendsdl3.cpp @@ -0,0 +1,34 @@ +#include + +#ifdef EE_BACKEND_SDL3 + +#if ( EE_PLATFORM == EE_PLATFORM_ANDROID || EE_PLATFORM == EE_PLATFORM_IOS ) && defined( main ) +#undef main +#endif + +#if EE_PLATFORM != EE_PLATFORM_ANDROID && EE_PLATFORM != EE_PLATFORM_IOS && \ + EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN && !defined( EE_COMPILER_MSVC ) && \ + !defined( EE_SDL3_FROM_ROOTPATH ) +#include +#else +#include +#endif + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +WindowBackendSDL3::WindowBackendSDL3() : WindowBackendLibrary() { + SDL_SetHint( SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0" ); + // The following hints are not available in SDL3 (or renamed) + // SDL_SetHint( SDL_HINT_IME_SHOW_UI, "1" ); + // SDL_SetHint( SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, "1" ); +} + +WindowBackendSDL3::~WindowBackendSDL3() { +#if EE_PLATFORM != EE_PLATFORM_MACOS + SDL_Quit(); +#endif +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/backendsdl3.hpp b/src/eepp/window/backend/SDL3/backendsdl3.hpp new file mode 100644 index 000000000..0457bafc8 --- /dev/null +++ b/src/eepp/window/backend/SDL3/backendsdl3.hpp @@ -0,0 +1,25 @@ +#ifndef EE_WINDOWCBACKENDSDL3_HPP +#define EE_WINDOWCBACKENDSDL3_HPP + +#include +#include + +#ifdef EE_BACKEND_SDL3 + +#include +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API WindowBackendSDL3 : public WindowBackendLibrary { + public: + WindowBackendSDL3(); + + ~WindowBackendSDL3(); +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif + +#endif diff --git a/src/eepp/window/backend/SDL3/base.hpp b/src/eepp/window/backend/SDL3/base.hpp new file mode 100644 index 000000000..f5c101172 --- /dev/null +++ b/src/eepp/window/backend/SDL3/base.hpp @@ -0,0 +1,32 @@ +#ifndef EE_WINDOWBACKEND_BASE_SDL3_HPP +#define EE_WINDOWBACKEND_BASE_SDL3_HPP + +#include + +#ifdef EE_BACKEND_SDL_ACTIVE + +#if defined( EE_SDL_VERSION_3 ) +#ifndef EE_BACKEND_SDL3 +#define EE_BACKEND_SDL3 +#endif + +#if ( EE_PLATFORM == EE_PLATFORM_ANDROID || EE_PLATFORM == EE_PLATFORM_IOS ) && defined( main ) +#undef main +#endif + +#if EE_PLATFORM != EE_PLATFORM_ANDROID && EE_PLATFORM != EE_PLATFORM_IOS && \ + EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN && !defined( EE_COMPILER_MSVC ) && \ + !defined( EE_SDL3_FROM_ROOTPATH ) +#include +#else +#include +#endif +#else +#ifndef EE_BACKEND_SDL_1_2 +#define EE_BACKEND_SDL_1_2 +#endif +#endif + +#endif + +#endif diff --git a/src/eepp/window/backend/SDL3/clipboardsdl3.cpp b/src/eepp/window/backend/SDL3/clipboardsdl3.cpp new file mode 100644 index 000000000..e93d996d6 --- /dev/null +++ b/src/eepp/window/backend/SDL3/clipboardsdl3.cpp @@ -0,0 +1,45 @@ +#include +#include +#include + +#ifdef EE_BACKEND_SDL3 + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +ClipboardSDL::ClipboardSDL( EE::Window::Window* window ) : Clipboard( window ) {} + +ClipboardSDL::~ClipboardSDL() {} + +void ClipboardSDL::init() {} + +void ClipboardSDL::setText( const std::string& text ) { + SDL_SetClipboardText( text.c_str() ); +} + +std::string ClipboardSDL::getText() { + char* text = SDL_GetClipboardText(); + std::string str( text ? text : "" ); + SDL_free( text ); + return str; +} + +bool ClipboardSDL::hasPrimarySelection() const { + // SDL3 may not have primary selection support + return false; +} + +std::string ClipboardSDL::getPrimarySelectionText() { + return getText(); +} + +void ClipboardSDL::setPrimarySelectionText( const std::string& text ) { + // No-op +} + +String ClipboardSDL::getWideText() { + return String::fromUtf8( getText() ); +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/clipboardsdl3.hpp b/src/eepp/window/backend/SDL3/clipboardsdl3.hpp new file mode 100644 index 000000000..b2f3cbedf --- /dev/null +++ b/src/eepp/window/backend/SDL3/clipboardsdl3.hpp @@ -0,0 +1,41 @@ +#ifndef EE_WINDOWCCLIPBOARDSDL3_HPP +#define EE_WINDOWCCLIPBOARDSDL3_HPP + +#include +#include + +#ifdef EE_BACKEND_SDL3 + +#include +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API ClipboardSDL : public Clipboard { + public: + virtual ~ClipboardSDL(); + + std::string getText(); + + String getWideText(); + + void setText( const std::string& text ); + + bool hasPrimarySelection() const; + + std::string getPrimarySelectionText(); + + void setPrimarySelectionText( const std::string& text ); + + protected: + friend class WindowSDL; + + ClipboardSDL( EE::Window::Window* window ); + + void init(); +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif +#endif diff --git a/src/eepp/window/backend/SDL3/cursormanagersdl3.cpp b/src/eepp/window/backend/SDL3/cursormanagersdl3.cpp new file mode 100644 index 000000000..b7ddc6806 --- /dev/null +++ b/src/eepp/window/backend/SDL3/cursormanagersdl3.cpp @@ -0,0 +1,85 @@ +#include +#include + +#ifdef EE_BACKEND_SDL3 + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +static SDL_Cursor* SDL_SYS_CURSORS[Cursor::SysCursorCount] = { 0 }; + +static SDL_Cursor* getLoadCursor( const Cursor::SysType& cursor ) { + if ( 0 == SDL_SYS_CURSORS[cursor] ) { + SDL_SYS_CURSORS[cursor] = SDL_CreateSystemCursor( (SDL_SystemCursor)cursor ); + } + return SDL_SYS_CURSORS[cursor]; +} + +CursorManagerSDL::CursorManagerSDL( EE::Window::Window* window ) : CursorManager( window ) {} + +Cursor* CursorManagerSDL::create( Texture* tex, const Vector2i& hotspot, const std::string& name ) { + return eeNew( CursorSDL, ( tex, hotspot, name, mWindow ) ); +} + +Cursor* CursorManagerSDL::create( Image* img, const Vector2i& hotspot, const std::string& name ) { + return eeNew( CursorSDL, ( img, hotspot, name, mWindow ) ); +} + +Cursor* CursorManagerSDL::create( const std::string& path, const Vector2i& hotspot, + const std::string& name ) { + return eeNew( CursorSDL, ( path, hotspot, name, mWindow ) ); +} + +void CursorManagerSDL::set( Cursor* cursor ) { + if ( NULL != cursor && cursor != mCurrent ) { + SDL_SetCursor( reinterpret_cast( cursor )->GetCursor() ); + mCurrent = cursor; + mCurSysCursor = false; + mSysCursor = Cursor::SysCursorNone; + } +} + +void CursorManagerSDL::set( Cursor::SysType syscurid ) { + if ( syscurid != mSysCursor ) { + SDL_SetCursor( getLoadCursor( syscurid ) ); + mCurrent = NULL; + mCurSysCursor = true; + mSysCursor = syscurid; + } +} + +void CursorManagerSDL::show() { + setVisible( true ); +} + +void CursorManagerSDL::hide() { + setVisible( false ); +} + +void CursorManagerSDL::setVisible( bool visible ) { + if ( visible ) + SDL_ShowCursor(); + else + SDL_HideCursor(); + mVisible = visible; +} + +void CursorManagerSDL::remove( Cursor* cursor, bool Delete ) { + CursorManager::remove( cursor, Delete ); +} + +void CursorManagerSDL::reload() { + if ( mVisible ) { + show(); + if ( mCurSysCursor ) { + set( mSysCursor ); + } else { + set( mCurrent ); + } + } else { + hide(); + } +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/cursormanagersdl3.hpp b/src/eepp/window/backend/SDL3/cursormanagersdl3.hpp new file mode 100644 index 000000000..d955df390 --- /dev/null +++ b/src/eepp/window/backend/SDL3/cursormanagersdl3.hpp @@ -0,0 +1,41 @@ +#ifndef EE_WINDOWCCURSORMANAGERSDL3_HPP +#define EE_WINDOWCCURSORMANAGERSDL3_HPP + +#include +#include + +#ifdef EE_BACKEND_SDL3 + +using namespace EE::Window; + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API CursorManagerSDL : public CursorManager { + public: + CursorManagerSDL( EE::Window::Window* window ); + + Cursor* create( Texture* tex, const Vector2i& hotspot, const std::string& name ); + + Cursor* create( Image* img, const Vector2i& hotspot, const std::string& name ); + + Cursor* create( const std::string& path, const Vector2i& hotspot, const std::string& name ); + + void set( Cursor* cursor ); + + void set( Cursor::SysType syscurid ); + + void show(); + + void hide(); + + void setVisible( bool visible ); + + void remove( Cursor* cursor, bool Delete = false ); + + void reload(); +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif +#endif diff --git a/src/eepp/window/backend/SDL3/cursorsdl3.cpp b/src/eepp/window/backend/SDL3/cursorsdl3.cpp new file mode 100644 index 000000000..7b2b52a95 --- /dev/null +++ b/src/eepp/window/backend/SDL3/cursorsdl3.cpp @@ -0,0 +1,65 @@ +#include + +#ifdef EE_BACKEND_SDL3 + +#include +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +CursorSDL::CursorSDL( Texture* tex, const Vector2i& hotspot, const std::string& name, + EE::Window::Window* window ) : + Cursor( tex, hotspot, name, window ), mCursor( nullptr ) { + create(); +} + +CursorSDL::CursorSDL( Graphics::Image* img, const Vector2i& hotspot, const std::string& name, + EE::Window::Window* window ) : + Cursor( img, hotspot, name, window ), mCursor( nullptr ) { + create(); +} + +CursorSDL::CursorSDL( const std::string& path, const Vector2i& hotspot, const std::string& name, + EE::Window::Window* window ) : + Cursor( path, hotspot, name, window ), mCursor( nullptr ) { + create(); +} + +CursorSDL::~CursorSDL() { + if ( nullptr != mCursor ) + SDL_DestroyCursor( mCursor ); +} + +void CursorSDL::create() { + if ( nullptr == mImage ) + return; + + int x = mImage->getWidth(); + int y = mImage->getHeight(); + int c = mImage->getChannels(); + + SDL_Surface* surface = SDL_CreateSurface( x, y, SDL_PIXELFORMAT_RGBA32 ); + if ( !surface ) + return; + + Uint8* src = (Uint8*)mImage->getPixels(); + Uint8* dst = (Uint8*)surface->pixels; + int srcPitch = x * c; + int dstPitch = surface->pitch; + + for ( int row = 0; row < y; ++row ) { + memcpy( dst + row * dstPitch, src + row * srcPitch, srcPitch ); + } + + mCursor = SDL_CreateColorCursor( surface, mHotSpot.x, mHotSpot.y ); + + SDL_DestroySurface( surface ); +} + +SDL_Cursor* CursorSDL::GetCursor() const { + return mCursor; +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/cursorsdl3.hpp b/src/eepp/window/backend/SDL3/cursorsdl3.hpp new file mode 100644 index 000000000..ea20a4ca8 --- /dev/null +++ b/src/eepp/window/backend/SDL3/cursorsdl3.hpp @@ -0,0 +1,37 @@ +#ifndef EE_WINDOWCCURSORSDL3_HPP +#define EE_WINDOWCCURSORSDL3_HPP + +#include +#include + +#ifdef EE_BACKEND_SDL3 + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API CursorSDL : public Cursor { + public: + SDL_Cursor* GetCursor() const; + + protected: + friend class CursorManagerSDL; + + SDL_Cursor* mCursor; + + CursorSDL( Texture* tex, const Vector2i& hotspot, const std::string& name, + EE::Window::Window* window ); + + CursorSDL( Graphics::Image* img, const Vector2i& hotspot, const std::string& name, + EE::Window::Window* window ); + + CursorSDL( const std::string& path, const Vector2i& hotspot, const std::string& name, + EE::Window::Window* window ); + + virtual ~CursorSDL(); + + void create(); +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif +#endif diff --git a/src/eepp/window/backend/SDL3/displaymanagersdl3.cpp b/src/eepp/window/backend/SDL3/displaymanagersdl3.cpp new file mode 100644 index 000000000..fb10cea91 --- /dev/null +++ b/src/eepp/window/backend/SDL3/displaymanagersdl3.cpp @@ -0,0 +1,159 @@ +#include +#include + +#ifdef EE_BACKEND_SDL3 + +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +DisplaySDL3::DisplaySDL3( int index, SDL_DisplayID displayId ) : + Display( index ), mDisplayId( displayId ) {} + +std::string DisplaySDL3::getName() const { + return std::string( mDisplayId ? SDL_GetDisplayName( mDisplayId ) : "Unknown" ); +} + +Rect DisplaySDL3::getBounds() const { + SDL_Rect r{}; + if ( mDisplayId && SDL_GetDisplayBounds( mDisplayId, &r ) == 0 ) + return Rect( r.x, r.y, r.w, r.h ); + return Rect(); +} + +Rect DisplaySDL3::getUsableBounds() const { + SDL_Rect r{}; + if ( mDisplayId && SDL_GetDisplayUsableBounds( mDisplayId, &r ) == 0 ) + return Rect( r.x, r.y, r.w, r.h ); + return Rect(); +} + +Float DisplaySDL3::getDPI() { + float scale = 1.0f; + if ( mDisplayId ) { + scale = SDL_GetDisplayContentScale( mDisplayId ); + if ( scale <= 0 ) + scale = 1.0f; + } + return 96.0f * scale; +} + +const int& DisplaySDL3::getIndex() const { + return index; +} + +DisplayMode DisplaySDL3::getCurrentMode() const { + const SDL_DisplayMode* mode = mDisplayId ? SDL_GetCurrentDisplayMode( mDisplayId ) : nullptr; + if ( mode ) + return DisplayMode( mode->w, mode->h, static_cast( mode->refresh_rate ), index ); + return DisplayMode( 0, 0, 0, index ); +} + +DisplayMode DisplaySDL3::getClosestDisplayMode( DisplayMode wantedMode ) const { + if ( !mDisplayId ) + return DisplayMode( 0, 0, 0, index ); + + SDL_DisplayMode closest; + if ( SDL_GetClosestFullscreenDisplayMode( mDisplayId, wantedMode.Width, wantedMode.Height, + static_cast( wantedMode.RefreshRate ), true, + &closest ) ) { + return DisplayMode( closest.w, closest.h, static_cast( closest.refresh_rate ), index ); + } + return DisplayMode( 0, 0, 0, index ); +} + +const std::vector& DisplaySDL3::getModes() const { + if ( displayModes.empty() && mDisplayId ) { + int count = 0; + SDL_DisplayMode** modesArray = SDL_GetFullscreenDisplayModes( mDisplayId, &count ); + if ( modesArray ) { + for ( int i = 0; i < count; i++ ) { + SDL_DisplayMode* mode = modesArray[i]; + displayModes.push_back( DisplayMode( + mode->w, mode->h, static_cast( mode->refresh_rate ), index ) ); + } + SDL_free( modesArray ); + } + } + return displayModes; +} + +Uint32 DisplaySDL3::getRefreshRate() const { + return getCurrentMode().RefreshRate; +} + +Sizeu DisplaySDL3::getSize() const { + DisplayMode mode = getCurrentMode(); + return { static_cast( mode.Width ), static_cast( mode.Height ) }; +} + +int DisplayManagerSDL3::getDisplayCount() { + if ( mDisplayIds.empty() ) { + // Initialize SDL video if not already done, similar to SDL2 backend + if ( !SDL_WasInit( SDL_INIT_VIDEO ) && !SDL_Init( SDL_INIT_VIDEO ) ) { + Log::error( "DisplayManagerSDL3: Failed to initialize SDL video: %s", SDL_GetError() ); + return 0; + } + int count = 0; + SDL_DisplayID* ids = SDL_GetDisplays( &count ); + if ( ids ) { + mDisplayIds.assign( ids, ids + count ); + SDL_free( ids ); + } + } + return static_cast( mDisplayIds.size() ); +} + +Display* DisplayManagerSDL3::getDisplayIndex( int index ) { + if ( displays.empty() ) { + int count = getDisplayCount(); + if ( count > 0 && !mDisplayIds.empty() ) { + for ( int i = 0; i < count; i++ ) { + displays.push_back( eeNew( DisplaySDL3, ( i, mDisplayIds[i] ) ) ); + } + } + } + return ( index >= 0 && index < (int)displays.size() ) ? displays[index] : nullptr; +} + +void DisplayManagerSDL3::enableScreenSaver() { + SDL_EnableScreenSaver(); + SDL_SetHint( SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1" ); +} + +void DisplayManagerSDL3::disableScreenSaver() { + SDL_DisableScreenSaver(); + SDL_SetHint( SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "0" ); +} + +void DisplayManagerSDL3::enableMouseFocusClickThrough() { + SDL_SetHint( SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1" ); +} + +void DisplayManagerSDL3::disableMouseFocusClickThrough() { + SDL_SetHint( SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "0" ); +} + +void DisplayManagerSDL3::disableBypassCompositor() { +#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR + SDL_SetHint( SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0" ); +#endif +} + +void DisplayManagerSDL3::enableBypassCompositor() { +#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR + SDL_SetHint( SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "1" ); +#endif +} + +int DisplayManagerSDL3::getDisplayIndexFromID( SDL_DisplayID id ) const { + for ( size_t i = 0; i < mDisplayIds.size(); ++i ) { + if ( mDisplayIds[i] == id ) + return static_cast( i ); + } + return -1; +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/displaymanagersdl3.hpp b/src/eepp/window/backend/SDL3/displaymanagersdl3.hpp new file mode 100644 index 000000000..1a99fff17 --- /dev/null +++ b/src/eepp/window/backend/SDL3/displaymanagersdl3.hpp @@ -0,0 +1,64 @@ +#ifndef EE_WINDOW_DISPLAYMANAGERSDL3_HPP +#define EE_WINDOW_DISPLAYMANAGERSDL3_HPP + +#include +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API DisplaySDL3 : public Display { + public: + DisplaySDL3( int index, SDL_DisplayID displayId ); + + std::string getName() const; + + Rect getBounds() const; + + Rect getUsableBounds() const; + + Float getDPI(); + + const int& getIndex() const; + + DisplayMode getCurrentMode() const; + + DisplayMode getClosestDisplayMode( DisplayMode wantedMode ) const; + + const std::vector& getModes() const; + + Uint32 getRefreshRate() const; + + Sizeu getSize() const; + + protected: + mutable std::vector displayModes; + SDL_DisplayID mDisplayId; +}; + +class EE_API DisplayManagerSDL3 : public DisplayManager { + public: + int getDisplayCount(); + + Display* getDisplayIndex( int index ); + + void enableScreenSaver(); + + void disableScreenSaver(); + + void enableMouseFocusClickThrough(); + + void disableMouseFocusClickThrough(); + + void disableBypassCompositor(); + + void enableBypassCompositor(); + + int getDisplayIndexFromID( SDL_DisplayID id ) const; + + protected: + std::vector mDisplayIds; +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/inputsdl3.cpp b/src/eepp/window/backend/SDL3/inputsdl3.cpp new file mode 100644 index 000000000..079883d32 --- /dev/null +++ b/src/eepp/window/backend/SDL3/inputsdl3.cpp @@ -0,0 +1,489 @@ +#include +#include +#include +#include +#include + +#ifdef EE_BACKEND_SDL3 + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +InputSDL::InputSDL( Window* window ) : + Input( window, eeNew( JoystickManagerSDL, () ) ), mDPIScale( 1.f ) { +#if defined( EE_X11_PLATFORM ) + mMouseSpeed = 1.75f; +#endif +} + +InputSDL::~InputSDL() {} + +void InputSDL::update() { + SDL_Event SDLEvent; + cleanStates(); + + ++mEventsSentId; + if ( mEventsSentId == std::numeric_limits::max() ) + mEventsSentId = 0; + + if ( !mQueuedEvents.empty() ) { + for ( const auto& prevEvent : mQueuedEvents ) + sendEvent( prevEvent ); + mQueuedEvents.clear(); + } + while ( SDL_PollEvent( &SDLEvent ) ) + sendEvent( SDLEvent ); + InputEvent endProcessingEvent; + endProcessingEvent.Type = InputEvent::EventsSent; + processEvent( &endProcessingEvent ); +} + +void InputSDL::waitEvent( const Time& timeout ) { + SDL_Event SDLEvent; + if ( timeout == Time::Zero ) { + if ( SDL_WaitEvent( &SDLEvent ) ) + mQueuedEvents.emplace_back( SDLEvent ); + } else if ( SDL_WaitEventTimeout( &SDLEvent, (int)timeout.asMilliseconds() ) ) { + if ( SDLEvent.type != SDL_EVENT_FIRST ) + mQueuedEvents.emplace_back( SDLEvent ); + } +} + +bool InputSDL::grabInput() { + SDL_Window* win = static_cast( mWindow )->getSDLWindow(); + return SDL_GetWindowMouseGrab( win ); +} + +void InputSDL::grabInput( const bool& Grab ) { + SDL_Window* win = static_cast( mWindow )->getSDLWindow(); + SDL_SetWindowMouseGrab( win, Grab ); +} + +void InputSDL::injectMousePos( const Uint16& x, const Uint16& y ) { + SDL_Window* win = static_cast( mWindow )->getSDLWindow(); + SDL_WarpMouseInWindow( win, static_cast( x ), static_cast( y ) ); +} + +Vector2i InputSDL::queryMousePos() { + Vector2i mousePos; + float tempMouseX, tempMouseY; + int tempWinPosX, tempWinPosY; + SDL_Window* win = static_cast( mWindow )->getSDLWindow(); + SDL_GetGlobalMouseState( &tempMouseX, &tempMouseY ); + SDL_GetWindowPosition( win, &tempWinPosX, &tempWinPosY ); + mousePos.x = static_cast( tempMouseX ) - tempWinPosX; + mousePos.y = static_cast( tempMouseY ) - tempWinPosY; + return mousePos; +} + +void InputSDL::captureMouse( const bool& capture ) { + SDL_CaptureMouse( capture ); +} + +bool InputSDL::isMouseCaptured() const { + SDL_Window* win = static_cast( mWindow )->getSDLWindow(); + return SDL_GetWindowFlags( win ) & SDL_WINDOW_MOUSE_CAPTURE; +} + +std::string InputSDL::getKeyName( const Keycode& keycode ) const { + return std::string( SDL_GetKeyName( static_cast( keycode ) ) ); +} + +Keycode InputSDL::getKeyFromName( const std::string& keycode ) const { + return static_cast( SDL_GetKeyFromName( keycode.c_str() ) ); +} + +std::string InputSDL::getScancodeName( const Scancode& scancode ) const { + return SDL_GetScancodeName( static_cast( scancode ) ); +} + +Scancode InputSDL::getScancodeFromName( const std::string& scancode ) const { + return static_cast( SDL_GetScancodeFromName( scancode.c_str() ) ); +} + +Keycode InputSDL::getKeyFromScancode( const Scancode& scancode ) const { + return static_cast( + SDL_GetKeyFromScancode( static_cast( scancode ), SDL_KMOD_NONE, 1 ) ); +} + +Scancode InputSDL::getScancodeFromKey( const Keycode& keycode ) const { + return static_cast( + SDL_GetScancodeFromKey( static_cast( keycode ), nullptr ) ); +} + +void InputSDL::init() { + mDPIScale = mWindow->getScale(); + mMousePos = queryMousePos(); +} + +void InputSDL::sendEvent( const SDL_Event& SDLEvent ) { + InputEvent event; + event.Type = InputEvent::NoEvent; + event.WinID = 0; + + switch ( SDLEvent.type ) { + case SDL_EVENT_WINDOW_SHOWN: { + event.Type = InputEvent::Window; + event.window.gain = 1; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowShown; + break; + } + case SDL_EVENT_WINDOW_HIDDEN: { + event.Type = InputEvent::Window; + event.window.gain = 0; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowHidden; + break; + } + case SDL_EVENT_WINDOW_EXPOSED: { + event.Type = InputEvent::VideoExpose; + event.WinID = SDLEvent.window.windowID; + event.expose.type = event.Type; + break; + } + case SDL_EVENT_WINDOW_MOVED: { + event.Type = InputEvent::Window; + event.window.gain = 1; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowMoved; + break; + } + case SDL_EVENT_WINDOW_RESIZED: { + event.Type = InputEvent::VideoResize; + event.WinID = SDLEvent.window.windowID; + mDPIScale = mWindow->getScale(); + event.resize.w = static_cast( SDLEvent.window.data1 * mDPIScale ); + event.resize.h = static_cast( SDLEvent.window.data2 * mDPIScale ); + break; + } + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: { + event.Type = InputEvent::Window; + event.window.gain = 1; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowSizeChanged; + break; + } + case SDL_EVENT_WINDOW_MINIMIZED: { + event.Type = InputEvent::Window; + event.window.gain = 0; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowMinimized; + break; + } + case SDL_EVENT_WINDOW_MAXIMIZED: { + event.Type = InputEvent::Window; + event.window.gain = 1; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowMaximized; + break; + } + case SDL_EVENT_WINDOW_RESTORED: { + event.Type = InputEvent::Window; + event.window.gain = 1; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowRestored; + break; + } + case SDL_EVENT_WINDOW_MOUSE_ENTER: { + event.Type = InputEvent::Window; + event.window.gain = 1; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowMouseEnter; + break; + } + case SDL_EVENT_WINDOW_MOUSE_LEAVE: { + event.Type = InputEvent::Window; + event.window.gain = 0; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowMouseLeave; + break; + } + case SDL_EVENT_WINDOW_FOCUS_GAINED: { + event.Type = InputEvent::Window; + event.window.gain = 1; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowKeyboardFocusGain; + break; + } + case SDL_EVENT_WINDOW_FOCUS_LOST: { + event.Type = InputEvent::Window; + event.window.gain = 0; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowKeyboardFocusLost; + break; + } + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: { + event.Type = InputEvent::Window; + event.window.gain = 0; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowClose; + break; + } + case SDL_EVENT_WINDOW_HIT_TEST: { + event.Type = InputEvent::Window; + event.window.gain = 1; + event.WinID = SDLEvent.window.windowID; + event.window.type = InputEvent::WindowHitTest; + break; + } + case SDL_EVENT_TEXT_INPUT: { + String txt = String::fromUtf8( std::string_view{ SDLEvent.text.text } ); + event.Type = InputEvent::TextInput; + // Convert from nanoseconds (SDL3) to milliseconds (EE framework) + event.text.timestamp = static_cast( SDLEvent.text.timestamp / 1000000ULL ); + event.WinID = SDLEvent.text.windowID; + for ( size_t i = 0; i < txt.size() - 1; i++ ) { + event.text.text = txt[i]; + processEvent( &event ); + } + event.text.text = txt[txt.size() - 1]; + break; + } + case SDL_EVENT_TEXT_EDITING: { + event.Type = InputEvent::TextEditing; + event.textediting.text = SDLEvent.edit.text; + event.textediting.start = SDLEvent.edit.start; + event.textediting.length = SDLEvent.edit.length; + event.WinID = SDLEvent.edit.windowID; + break; + } + case SDL_EVENT_KEY_DOWN: { + event.Type = InputEvent::KeyDown; + event.key.state = SDLEvent.key.down ? 1 : 0; + event.key.which = SDLEvent.key.windowID; + event.key.keysym.sym = static_cast( SDLEvent.key.key ); + event.key.keysym.scancode = static_cast( SDLEvent.key.scancode ); + event.key.keysym.mod = SDLEvent.key.mod; + event.key.keysym.unicode = 0; + event.WinID = SDLEvent.key.windowID; + break; + } + case SDL_EVENT_KEY_UP: { + event.Type = InputEvent::KeyUp; + event.key.state = SDLEvent.key.down ? 1 : 0; + event.key.which = SDLEvent.key.windowID; + event.key.keysym.sym = static_cast( SDLEvent.key.key ); + event.key.keysym.scancode = static_cast( SDLEvent.key.scancode ); + event.key.keysym.mod = SDLEvent.key.mod; + event.key.keysym.unicode = 0; + event.WinID = SDLEvent.key.windowID; + break; + } + case SDL_EVENT_MOUSE_MOTION: { + event.Type = InputEvent::MouseMotion; + event.motion.which = SDLEvent.motion.windowID; + event.motion.state = SDLEvent.motion.state; + event.motion.x = static_cast( SDLEvent.motion.x * mDPIScale ); + event.motion.y = static_cast( SDLEvent.motion.y * mDPIScale ); + event.motion.xrel = static_cast( SDLEvent.motion.xrel * mDPIScale ); + event.motion.yrel = static_cast( SDLEvent.motion.yrel * mDPIScale ); + event.WinID = SDLEvent.motion.windowID; + break; + } + case SDL_EVENT_MOUSE_BUTTON_DOWN: { + event.Type = InputEvent::MouseButtonDown; + event.button.button = SDLEvent.button.button; + event.button.which = SDLEvent.button.windowID; + event.button.state = SDLEvent.button.down ? 1 : 0; + event.button.x = static_cast( SDLEvent.button.x * mDPIScale ); + event.button.y = static_cast( SDLEvent.button.y * mDPIScale ); + event.WinID = SDLEvent.button.windowID; + break; + } + case SDL_EVENT_MOUSE_BUTTON_UP: { + event.Type = InputEvent::MouseButtonUp; + event.button.button = SDLEvent.button.button; + event.button.which = SDLEvent.button.windowID; + event.button.state = SDLEvent.button.down ? 1 : 0; + event.button.x = static_cast( SDLEvent.button.x * mDPIScale ); + event.button.y = static_cast( SDLEvent.button.y * mDPIScale ); + event.WinID = SDLEvent.button.windowID; + break; + } + case SDL_EVENT_MOUSE_WHEEL: { + Uint8 button; + float x = SDLEvent.wheel.x; + float y = SDLEvent.wheel.y; + + if ( y == 0 && x == 0 ) + break; + + if ( y > 0 ) { + button = EE_BUTTON_WHEELUP; + } else if ( y < 0 ) { + button = EE_BUTTON_WHEELDOWN; + } else if ( x > 0 ) { + button = EE_BUTTON_WHEELRIGHT; + } else if ( x < 0 ) { + button = EE_BUTTON_WHEELLEFT; + } else { + return; + } + + // Get mouse position from the event (mouse_x, mouse_y are in window coordinates) + event.button.button = button; + event.button.x = static_cast( SDLEvent.wheel.mouse_x * mDPIScale ); + event.button.y = static_cast( SDLEvent.wheel.mouse_y * mDPIScale ); + event.button.which = SDLEvent.wheel.windowID; + event.WinID = SDLEvent.wheel.windowID; + + event.Type = InputEvent::MouseButtonDown; + event.button.state = 1; + processEvent( &event ); + + event.Type = InputEvent::MouseButtonUp; + event.button.state = 0; + processEvent( &event ); + + event.Type = InputEvent::MouseWheel; + event.wheel.which = SDLEvent.wheel.windowID; + event.wheel.direction = SDLEvent.wheel.direction == SDL_MOUSEWHEEL_NORMAL + ? InputEvent::WheelEvent::Normal + : InputEvent::WheelEvent::Flipped; + event.wheel.x = SDLEvent.wheel.x; + event.wheel.y = SDLEvent.wheel.y; + processEvent( &event ); + break; + } + case SDL_EVENT_JOYSTICK_AXIS_MOTION: { + event.Type = InputEvent::JoyAxisMotion; + event.jaxis.which = SDLEvent.jaxis.which; + event.jaxis.axis = SDLEvent.jaxis.axis; + event.jaxis.value = SDLEvent.jaxis.value; + break; + } + case SDL_EVENT_JOYSTICK_BALL_MOTION: { + event.Type = InputEvent::JoyBallMotion; + event.jball.which = SDLEvent.jball.which; + event.jball.ball = SDLEvent.jball.ball; + event.jball.xrel = SDLEvent.jball.xrel; + event.jball.yrel = SDLEvent.jball.yrel; + break; + } + case SDL_EVENT_JOYSTICK_HAT_MOTION: { + event.Type = InputEvent::JoyHatMotion; + event.jhat.which = SDLEvent.jhat.which; + event.jhat.value = SDLEvent.jhat.value; + event.jhat.hat = SDLEvent.jhat.hat; + break; + } + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: { + event.Type = InputEvent::JoyButtonDown; + event.jbutton.which = SDLEvent.jbutton.which; + event.jbutton.state = SDLEvent.jbutton.down ? 1 : 0; + event.jbutton.button = SDLEvent.jbutton.button; + break; + } + case SDL_EVENT_JOYSTICK_BUTTON_UP: { + event.Type = InputEvent::JoyButtonUp; + event.jbutton.which = SDLEvent.jbutton.which; + event.jbutton.state = SDLEvent.jbutton.down ? 1 : 0; + event.jbutton.button = SDLEvent.jbutton.button; + break; + } + case SDL_EVENT_JOYSTICK_ADDED: { + // Could be used to dynamically add joysticks, but JoystickManager handles it + break; + } + case SDL_EVENT_JOYSTICK_REMOVED: { + // Could be used to dynamically remove joysticks + break; + } + case SDL_EVENT_QUIT: { + event.Type = InputEvent::Quit; + event.quit.type = event.Type; + break; + } + case SDL_EVENT_DROP_FILE: { + event.Type = InputEvent::FileDropped; + event.file.file = SDLEvent.drop.data; + event.WinID = SDLEvent.drop.windowID; + break; + } + case SDL_EVENT_DROP_TEXT: { + event.Type = InputEvent::TextDropped; + event.textdrop.text = SDLEvent.drop.data; + event.WinID = SDLEvent.drop.windowID; + break; + } + default: { + if ( SDLEvent.type >= SDL_EVENT_USER && SDLEvent.type < SDL_EVENT_LAST ) { + event.Type = InputEvent::EventUser + SDLEvent.type - SDL_EVENT_USER; + event.user.type = event.Type; + event.user.code = SDLEvent.user.code; + event.user.data1 = SDLEvent.user.data1; + event.user.data2 = SDLEvent.user.data2; + event.WinID = SDLEvent.user.windowID; + } else { + event.Type = InputEvent::NoEvent; + } + } + } + + // Convert SDL3 joystick ID (which is a handle) to index for framework compatibility + if ( event.Type == InputEvent::JoyAxisMotion || event.Type == InputEvent::JoyBallMotion || + event.Type == InputEvent::JoyHatMotion || event.Type == InputEvent::JoyButtonDown || + event.Type == InputEvent::JoyButtonUp ) { + SDL_JoystickID joyId = 0; + switch ( event.Type ) { + case InputEvent::JoyAxisMotion: + joyId = SDLEvent.jaxis.which; + break; + case InputEvent::JoyBallMotion: + joyId = SDLEvent.jball.which; + break; + case InputEvent::JoyHatMotion: + joyId = SDLEvent.jhat.which; + break; + case InputEvent::JoyButtonDown: + case InputEvent::JoyButtonUp: + joyId = SDLEvent.jbutton.which; + break; + default: + joyId = 0; + break; + } + auto* mgr = static_cast( mJoystickManager ); + Uint32 idx = mgr->getIndexFromID( joyId ); + if ( UINT32_MAX == idx ) { + // Unknown joystick ID, drop event + return; + } + // Overwrite the 'which' field with the correct index + switch ( event.Type ) { + case InputEvent::JoyAxisMotion: + event.jaxis.which = idx; + break; + case InputEvent::JoyBallMotion: + event.jball.which = idx; + break; + case InputEvent::JoyHatMotion: + event.jhat.which = idx; + break; + case InputEvent::JoyButtonDown: + case InputEvent::JoyButtonUp: + event.jbutton.which = idx; + break; + default: + break; + } + } + + EE::Window::Window* win = nullptr; + + if ( InputEvent::NoEvent != event.Type ) { + if ( event.WinID == mWindow->getWindowID() || event.WinID == 0 ) { + processEvent( &event ); + } else if ( ( win = EE::Window::Engine::instance()->getWindowID( event.WinID ) ) ) { + win->getInput()->processEvent( &event ); + } else { + processEvent( &event ); + } + } + + // In SDL3, drop event data is managed by SDL, do not free +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/inputsdl3.hpp b/src/eepp/window/backend/SDL3/inputsdl3.hpp new file mode 100644 index 000000000..87ca5b293 --- /dev/null +++ b/src/eepp/window/backend/SDL3/inputsdl3.hpp @@ -0,0 +1,61 @@ +#ifndef EE_WINDOWCINPUTSDL3_HPP +#define EE_WINDOWCINPUTSDL3_HPP + +#include +#include + +#ifdef EE_BACKEND_SDL3 + +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API InputSDL : public Input { + public: + ~InputSDL(); + + void update(); + + void waitEvent( const Time& timeout = Time::Zero ); + + bool grabInput(); + + void grabInput( const bool& Grab ); + + void injectMousePos( const Uint16& x, const Uint16& y ); + + Vector2i queryMousePos(); + + void captureMouse( const bool& capture ); + + bool isMouseCaptured() const; + + std::string getKeyName( const Keycode& keycode ) const; + + Keycode getKeyFromName( const std::string& keycode ) const; + + std::string getScancodeName( const Scancode& scancode ) const; + + Scancode getScancodeFromName( const std::string& scancode ) const; + + Keycode getKeyFromScancode( const Scancode& scancode ) const; + + Scancode getScancodeFromKey( const Keycode& scancode ) const; + + protected: + friend class WindowSDL; + Float mDPIScale; + std::vector mQueuedEvents; + + InputSDL( EE::Window::Window* window ); + + void init(); + + void sendEvent( const SDL_Event& SDLEvent ); +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif + +#endif diff --git a/src/eepp/window/backend/SDL3/joystickmanagersdl3.cpp b/src/eepp/window/backend/SDL3/joystickmanagersdl3.cpp new file mode 100644 index 000000000..9b07e492a --- /dev/null +++ b/src/eepp/window/backend/SDL3/joystickmanagersdl3.cpp @@ -0,0 +1,92 @@ +#include +#include + +#ifdef EE_BACKEND_SDL3 + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +namespace { +void closeSubsystem() { +#if EE_PLATFORM != EE_PLATFORM_MACOS && EE_PLATFORM != EE_PLATFORM_IOS + if ( SDL_WasInit( SDL_INIT_JOYSTICK ) ) + SDL_QuitSubSystem( SDL_INIT_JOYSTICK ); +#endif +} +} // namespace + +JoystickManagerSDL::JoystickManagerSDL() : + JoystickManager(), mAsyncInit( &JoystickManagerSDL::openAsync, this ) {} + +JoystickManagerSDL::~JoystickManagerSDL() { + for ( Uint32 i = 0; i < mCount; i++ ) + eeSAFE_DELETE( mJoysticks[i] ); + closeSubsystem(); + mInit = false; +} + +void JoystickManagerSDL::update() { + if ( mInit ) { + SDL_UpdateJoysticks(); + for ( Uint32 i = 0; i < mCount; i++ ) + if ( nullptr != mJoysticks[i] ) + mJoysticks[i]->update(); + } +} + +void JoystickManagerSDL::openAsync() { + Sys::sleep( Milliseconds( 500 ) ); + + int error = SDL_InitSubSystem( SDL_INIT_JOYSTICK ); + + if ( !error ) { + int count = 0; + SDL_JoystickID* ids = SDL_GetJoysticks( &count ); + if ( ids ) { + mIds.assign( ids, ids + count ); + mCount = static_cast( count ); + SDL_free( ids ); + } else { + mCount = 0; + } + + // Build ID -> index mapping + mIdToIndex.clear(); + for ( Uint32 i = 0; i < mCount; i++ ) { + mIdToIndex[mIds[i]] = i; + create( i ); + } + + mInit = true; + + if ( mOpenCb ) + mOpenCb(); + } +} + +void JoystickManagerSDL::open( OpenCb openCb ) { + mOpenCb = openCb; + mAsyncInit.launch(); +} + +void JoystickManagerSDL::close() { + closeSubsystem(); + mInit = false; +} + +void JoystickManagerSDL::create( const Uint32& index ) { + if ( nullptr != mJoysticks[index] ) + mJoysticks[index]->reOpen(); + else + mJoysticks[index] = eeNew( JoystickSDL, ( index, mIds[index] ) ); +} + +Uint32 JoystickManagerSDL::getIndexFromID( SDL_JoystickID id ) const { + auto it = mIdToIndex.find( id ); + if ( it != mIdToIndex.end() ) + return it->second; + return UINT32_MAX; +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/joystickmanagersdl3.hpp b/src/eepp/window/backend/SDL3/joystickmanagersdl3.hpp new file mode 100644 index 000000000..f5dffd519 --- /dev/null +++ b/src/eepp/window/backend/SDL3/joystickmanagersdl3.hpp @@ -0,0 +1,46 @@ +#ifndef EE_WINDOWCJOYSTICKMANAGERSDL3_HPP +#define EE_WINDOWCJOYSTICKMANAGERSDL3_HPP + +#include +#include + +#ifdef EE_BACKEND_SDL3 + +#include +#include +#include +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API JoystickManagerSDL : public JoystickManager { + public: + JoystickManagerSDL(); + + virtual ~JoystickManagerSDL(); + + void update(); + + void close(); + + void open( OpenCb openCb = nullptr ); + + protected: + void create( const Uint32& index ) override; + + void openAsync(); + + Thread mAsyncInit; + bool mInit{ false }; + Uint32 mCount{ 0 }; + std::vector mIds; + std::unordered_map mIdToIndex; + +public: + Uint32 getIndexFromID( SDL_JoystickID id ) const; +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif +#endif diff --git a/src/eepp/window/backend/SDL3/joysticksdl3.cpp b/src/eepp/window/backend/SDL3/joysticksdl3.cpp new file mode 100644 index 000000000..61524317e --- /dev/null +++ b/src/eepp/window/backend/SDL3/joysticksdl3.cpp @@ -0,0 +1,70 @@ +#include + +#ifdef EE_BACKEND_SDL3 + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +JoystickSDL::JoystickSDL( const Uint32& index, SDL_JoystickID id ) : Joystick( index ), mJoystick( nullptr ), mId( id ) { + open(); +} + +JoystickSDL::~JoystickSDL() { + close(); +} + +void JoystickSDL::open() { + mJoystick = SDL_OpenJoystick( mId ); + if ( nullptr != mJoystick ) { + mName = SDL_GetJoystickName( mJoystick ); + mHats = SDL_GetNumJoystickHats( mJoystick ); + mButtons = eemin( SDL_GetNumJoystickButtons( mJoystick ), 32 ); + mAxes = SDL_GetNumJoystickAxes( mJoystick ); + mBalls = SDL_GetNumJoystickBalls( mJoystick ); + mButtonDown = mButtonDownLast = mButtonUp = 0; + } +} + +void JoystickSDL::close() { + if ( nullptr != mJoystick ) + SDL_CloseJoystick( mJoystick ); + mJoystick = nullptr; + mName = ""; + mHats = mButtons = mAxes = mBalls = 0; +} + +void JoystickSDL::update() { + if ( nullptr != mJoystick ) { + clearStates(); + for ( Int32 i = 0; i < mButtons; i++ ) { + updateButton( i, 0 != SDL_GetJoystickButton( mJoystick, i ) ); + } + } +} + +Uint8 JoystickSDL::getHat( const Int32& index ) { + if ( index >= 0 && index < mHats ) + return SDL_GetJoystickHat( mJoystick, index ); + return HAT_CENTERED; +} + +Float JoystickSDL::getAxis( const Int32& axis ) { + if ( axis >= 0 && axis < mAxes ) { + return static_cast( SDL_GetJoystickAxis( mJoystick, axis ) ) / 32768.0f; + } + return 0; +} + +Vector2i JoystickSDL::getBallMotion( const Int32& ball ) { + Vector2i v; + if ( ball >= 0 && ball < mBalls ) + SDL_GetJoystickBall( mJoystick, ball, &v.x, &v.y ); + return v; +} + +bool JoystickSDL::isPlugged() const { + return nullptr != mJoystick; +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/joysticksdl3.hpp b/src/eepp/window/backend/SDL3/joysticksdl3.hpp new file mode 100644 index 000000000..24d56adc2 --- /dev/null +++ b/src/eepp/window/backend/SDL3/joysticksdl3.hpp @@ -0,0 +1,41 @@ +#ifndef EE_WINDOWCJOYSTICKSDL3_HPP +#define EE_WINDOWCJOYSTICKSDL3_HPP + +#include +#include + +#ifdef EE_BACKEND_SDL3 + +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API JoystickSDL : public Joystick { + public: + JoystickSDL( const Uint32& index, SDL_JoystickID id ); + + virtual ~JoystickSDL(); + + void close(); + + void open(); + + void update(); + + Uint8 getHat( const Int32& index ); + + Float getAxis( const Int32& axis ); + + Vector2i getBallMotion( const Int32& ball ); + + bool isPlugged() const; + + protected: + SDL_Joystick* mJoystick; + SDL_JoystickID mId; +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif +#endif diff --git a/src/eepp/window/backend/SDL3/platformhelpersdl3.cpp b/src/eepp/window/backend/SDL3/platformhelpersdl3.cpp new file mode 100644 index 000000000..79b9086d2 --- /dev/null +++ b/src/eepp/window/backend/SDL3/platformhelpersdl3.cpp @@ -0,0 +1,78 @@ +#include +#include +#include + +using namespace EE::System; + +#ifdef EE_BACKEND_SDL3 + +#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN +#include +EM_JS( void, emscripten_open_url, ( const char* msg ), + { window.open( UTF8ToString( msg ), 'blank' ); } ); +#endif + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +PlatformHelperSDL3::PlatformHelperSDL3() {} + +bool PlatformHelperSDL3::openURL( const std::string& url ) { +#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN + emscripten_open_url( url.c_str() ); + return true; +#else + bool res = SDL_OpenURL( url.c_str() ); + if ( !res ) { + Log::error( "PlatformHelperSDL3::openURL: Failed with error - %s", SDL_GetError() ); + } + return res; +#endif +} + +char* PlatformHelperSDL3::iconv( const char* tocode, const char* fromcode, const char* inbuf, + size_t inbytesleft ) { + return SDL_iconv_string( tocode, fromcode, inbuf, inbytesleft ); +} + +void PlatformHelperSDL3::iconvFree( char* buf ) { + SDL_free( buf ); +} + +#if EE_PLATFORM == EE_PLATFORM_ANDROID +void* PlatformHelperSDL3::getActivity() { + return SDL_AndroidGetActivity(); +} + +void* PlatformHelperSDL3::getJNIEnv() { + return SDL_AndroidGetJNIEnv(); +} + +std::string PlatformHelperSDL3::getExternalStoragePath() { + return std::string( SDL_AndroidGetExternalStoragePath() ); +} + +std::string PlatformHelperSDL3::getInternalStoragePath() { + return std::string( SDL_AndroidGetInternalStoragePath() ); +} + +std::string PlatformHelperSDL3::getApkPath() { + static std::string apkPath = ""; + if ( "" == apkPath ) { + // Simplified: use SDL_AndroidGetApkPath if available + apkPath = std::string( SDL_AndroidGetApkPath() ? SDL_AndroidGetApkPath() : "" ); + } + return apkPath; +} + +bool PlatformHelperSDL3::isExternalStorageReadable() { + return 0 != ( SDL_AndroidGetExternalStorageState() & SDL_ANDROID_EXTERNAL_STORAGE_READ ); +} + +bool PlatformHelperSDL3::isExternalStorageWritable() { + return 0 != ( SDL_AndroidGetExternalStorageState() & SDL_ANDROID_EXTERNAL_STORAGE_WRITE ); +} +#endif + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/platformhelpersdl3.hpp b/src/eepp/window/backend/SDL3/platformhelpersdl3.hpp new file mode 100644 index 000000000..425939dad --- /dev/null +++ b/src/eepp/window/backend/SDL3/platformhelpersdl3.hpp @@ -0,0 +1,38 @@ +#ifndef EE_PLATFORMHELPERSDL3_HPP +#define EE_PLATFORMHELPERSDL3_HPP + +#include +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API PlatformHelperSDL3 : public PlatformHelper { + public: + PlatformHelperSDL3(); + + bool openURL( const std::string& url ); + + char* iconv( const char* tocode, const char* fromcode, const char* inbuf, size_t inbytesleft ); + + void iconvFree( char* buf ); + +#if EE_PLATFORM == EE_PLATFORM_ANDROID + void* getActivity(); + + void* getJNIEnv(); + + std::string getExternalStoragePath(); + + std::string getInternalStoragePath(); + + std::string getApkPath(); + + bool isExternalStorageReadable(); + + bool isExternalStorageWritable(); +#endif +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/windowsdl3.cpp b/src/eepp/window/backend/SDL3/windowsdl3.cpp new file mode 100644 index 000000000..ed43fd922 --- /dev/null +++ b/src/eepp/window/backend/SDL3/windowsdl3.cpp @@ -0,0 +1,941 @@ +#include + +#ifdef EE_BACKEND_SDL3 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if EE_PLATFORM == EE_PLATFORM_WIN +#include +#endif + +#if EE_PLATFORM == EE_PLATFORM_WIN || EE_PLATFORM == EE_PLATFORM_MACOS || \ + defined( EE_X11_PLATFORM ) || EE_PLATFORM == EE_PLATFORM_IOS || \ + EE_PLATFORM == EE_PLATFORM_ANDROID || EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN +#define SDL3_THREADED_GLCONTEXT +#endif + +#if EE_PLATFORM == EE_PLATFORM_WIN +#include +#include +#include +#include +#include + +bool WindowsIsProcessRunning( const char* processName, bool killProcess = false ) { + bool exists = false; + PROCESSENTRY32 entry = {}; + entry.dwSize = sizeof( PROCESSENTRY32 ); + HANDLE snapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); + if ( Process32First( snapshot, &entry ) ) { + while ( Process32Next( snapshot, &entry ) ) { +#ifdef UNICODE + if ( EE::String( entry.szExeFile ).toUtf8() == std::string( processName ) ) { +#else + if ( !stricmp( entry.szExeFile, processName ) ) { +#endif + exists = true; + if ( killProcess ) { + HANDLE aProc = OpenProcess( PROCESS_TERMINATE, 0, entry.th32ProcessID ); + if ( aProc ) { + TerminateProcess( aProc, 9 ); + CloseHandle( aProc ); + } + } + break; + } + } + } + CloseHandle( snapshot ); + return exists; +} + +#ifndef ERROR_ELEVATION_REQUIRED +#define ERROR_ELEVATION_REQUIRED ( 740 ) +#endif + +bool WindowsProcessLaunch( std::string command, HWND windowHwnd ) { +#ifdef UNICODE + wchar_t expandedCmd[1024] = {}; +#else + char expandedCmd[1024] = {}; +#endif + static PROCESS_INFORMATION pi = {}; + static STARTUPINFO si = {}; + si.cb = sizeof( si ); +#if UNICODE + ExpandEnvironmentStrings( EE::String::fromUtf8( command ).toWideString().c_str(), expandedCmd, + 1024 ); +#else + ExpandEnvironmentStrings( command.c_str(), expandedCmd, 1024 ); +#endif + if ( CreateProcess( NULL, expandedCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) ) { + WaitForSingleObject( pi.hProcess, 10000 ); + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + return true; + } else { + DWORD error = GetLastError(); + if ( error == ERROR_ELEVATION_REQUIRED && 0 != windowHwnd ) { +#ifdef UNICODE + std::intptr_t res = reinterpret_cast( + ShellExecute( windowHwnd, L"open", expandedCmd, L"", NULL, SW_SHOWDEFAULT ) ); +#else + std::intptr_t res = reinterpret_cast( + ShellExecute( windowHwnd, "open", expandedCmd, "", NULL, SW_SHOWDEFAULT ) ); +#endif + if ( res <= 32 ) { + return false; + } + } + } + return false; +} + +DEFINE_GUID( CLSID_UIHostNoLaunch, 0x4CE576FA, 0x83DC, 0x4f88, 0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, + 0xE3, 0x76 ); + +DEFINE_GUID( IID_ITipInvocation, 0x37c994e7, 0x432b, 0x4834, 0xa2, 0xf7, 0xdc, 0xe1, 0xf1, 0x3b, + 0x83, 0x4b ); + +struct ITipInvocation : IUnknown { + virtual HRESULT STDMETHODCALLTYPE Toggle( HWND wnd ) = 0; +}; + +static bool WIN_OSK_VISIBLE = false; + +int showOSK( HWND windowHwnd ) { + if ( !WindowsIsProcessRunning( "TabTip.exe" ) ) { + WindowsIsProcessRunning( + "WindowsInternal.ComposableShell.Experiences.TextInput.InputApp.EXE", true ); + + std::string programFiles( Sys::getOSArchitecture() == "x64" ? "%ProgramW6432%" + : "%ProgramFiles(x86)%" ); + WindowsProcessLaunch( programFiles + "\\Common Files\\microsoft shared\\ink\\TabTip.exe", + windowHwnd ); + } + + CoInitialize( 0 ); + + ITipInvocation* tip; + CoCreateInstance( CLSID_UIHostNoLaunch, 0, CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER, + IID_ITipInvocation, (void**)&tip ); + if ( tip != NULL ) { + tip->Toggle( GetDesktopWindow() ); + tip->Release(); + WIN_OSK_VISIBLE = true; + } + + return 0; +} + +int hideOSK() { + WIN_OSK_VISIBLE = false; + return PostMessage( GetDesktopWindow(), WM_SYSCOMMAND, (int)SC_CLOSE, 0 ); +} + +bool isDarkModeEnabled() { + HKEY hKey; + DWORD value = 1; + DWORD valueSize = sizeof( value ); + + if ( RegOpenKeyExA( HKEY_CURRENT_USER, + "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, + KEY_READ, &hKey ) == ERROR_SUCCESS ) { + RegQueryValueExA( hKey, "AppsUseLightTheme", nullptr, nullptr, + reinterpret_cast( &value ), &valueSize ); + RegCloseKey( hKey ); + } + + return value == 0; +} + +typedef HRESULT( WINAPI* DwmSetWindowAttributeFunc )( HWND, DWORD, LPCVOID, DWORD ); + +constexpr DWORD DWMWA_USE_IMMERSIVE_DARK_MODE = 20; + +void setUserTheme( HWND hwnd ) { + HMODULE hDwmapi = LoadLibraryA( "dwmapi.dll" ); + if ( !hDwmapi ) { + return; + } + + auto DwmSetWindowAttribute = reinterpret_cast( + GetProcAddress( hDwmapi, "DwmSetWindowAttribute" ) ); + if ( !DwmSetWindowAttribute ) { + FreeLibrary( hDwmapi ); + return; + } + + BOOL darkMode = isDarkModeEnabled() ? TRUE : FALSE; + DwmSetWindowAttribute( hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &darkMode, sizeof( darkMode ) ); + + FreeLibrary( hDwmapi ); +} +#elif defined( EE_X11_PLATFORM ) +#include +#include + +static pid_t ONBOARD_PID = 0; + +void showOSK() { + if ( ONBOARD_PID == 0 ) { + if ( FileSystem::fileExists( "/usr/bin/onboard" ) ) { + pid_t pid = fork(); + + if ( pid == 0 ) { + execl( "/usr/bin/onboard", "onboard", NULL ); + } else if ( pid != -1 ) { + ONBOARD_PID = pid; + } + } else { + EE::System::Log::error( + "\"onboard\" must be installed to be able to use the On Screen Keyboard" ); + } + } +} + +void hideOSK() { + if ( ONBOARD_PID != 0 ) { + kill( ONBOARD_PID, SIGTERM ); + ONBOARD_PID = 0; + } +} +#endif + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +WindowSDL::WindowSDL( WindowSettings Settings, ContextSettings Context ) : + Window( Settings, Context, eeNew( ClipboardSDL, ( this ) ), eeNew( InputSDL, ( this ) ), + eeNew( CursorManagerSDL, ( this ) ) ), + mSDLWindow( NULL ), + mGLContext( NULL ), + mGLContextThread( NULL ), + mWMinfo( NULL ) { + create( Settings, Context ); +} + +WindowSDL::~WindowSDL() { + if ( NULL != mGLContext ) { + SDL_GL_DestroyContext( mGLContext ); + } + + if ( NULL != mGLContextThread ) { + SDL_GL_DestroyContext( mGLContextThread ); + } + + eeSAFE_DELETE( mWMinfo ); + + if ( NULL != mSDLWindow ) { + SDL_DestroyWindow( mSDLWindow ); + } + +#if defined( EE_X11_PLATFORM ) + hideOSK(); +#endif +} + +bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) { + if ( mWindow.Created ) + return false; + + mWindow.WindowConfig = Settings; + mWindow.ContextConfig = Context; + + if ( !SDL_WasInit( SDL_INIT_VIDEO ) && !SDL_Init( SDL_INIT_VIDEO ) ) { + Log::error( "Unable to initialize SDL: %s", SDL_GetError() ); + + logFailureInit( "WindowSDL", getVersion() ); + + return false; + } + + SDL_DisplayID primaryDisplay = SDL_GetPrimaryDisplay(); + const SDL_DisplayMode* dpm = SDL_GetDesktopDisplayMode( primaryDisplay ); + + if ( dpm ) { + mWindow.DesktopResolution = Sizei( dpm->w, dpm->h ); + } else { + Log::error( "Failed to get desktop display mode" ); + mWindow.DesktopResolution = Sizei( 800, 600 ); + } + +#if EE_PLATFORM == EE_PLATFORM_ANDROID + mWindow.WindowConfig.Style = WindowStyle::Fullscreen | WindowStyle::UseDesktopResolution; +#endif + + if ( mWindow.WindowConfig.Style & WindowStyle::UseDesktopResolution ) { + mWindow.WindowConfig.Width = mWindow.DesktopResolution.getWidth(); + mWindow.WindowConfig.Height = mWindow.DesktopResolution.getHeight(); + } + + mWindow.Flags = SDL_WINDOW_OPENGL | + ( ( !mWindow.WindowConfig.DisableHiDPI ? SDL_WINDOW_HIGH_PIXEL_DENSITY : 0 ) ); + + if ( mWindow.WindowConfig.Style & WindowStyle::Resize ) { + mWindow.Flags |= SDL_WINDOW_RESIZABLE; + } + + if ( mWindow.WindowConfig.Style & WindowStyle::Borderless ) { + mWindow.Flags |= SDL_WINDOW_BORDERLESS; + } + + setGLConfig(); + + Uint32 tmpFlags = mWindow.Flags; + + if ( mWindow.WindowConfig.Style & WindowStyle::Fullscreen ) { + tmpFlags |= SDL_WINDOW_FULLSCREEN; + } + + if ( mWindow.ContextConfig.Multisamples > 0 ) { + SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, 1 ); + SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, mWindow.ContextConfig.Multisamples ); + } + +#if EE_PLATFORM != EE_PLATFORM_MACOS && EE_PLATFORM != EE_PLATFORM_IOS && \ + EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN + mWindow.WindowConfig.Width *= mWindow.WindowConfig.PixelDensity; + mWindow.WindowConfig.Height *= mWindow.WindowConfig.PixelDensity; +#endif + + mSDLWindow = SDL_CreateWindow( mWindow.WindowConfig.Title.c_str(), mWindow.WindowConfig.Width, + mWindow.WindowConfig.Height, tmpFlags ); + + if ( NULL == mSDLWindow ) { + Log::error( "Unable to create window: %s", SDL_GetError() ); + + logFailureInit( "WindowSDL", getVersion() ); + + return false; + } + +#if EE_PLATFORM == EE_PLATFORM_ANDROID || EE_PLATFORM == EE_PLATFORM_IOS + Log::notice( "Choosing GL Version from: %d", Context.Version ); + + if ( GLv_default != Context.Version ) { + if ( GLv_ES1 == Context.Version || GLv_2 == Context.Version ) { + if ( GLv_2 == Context.Version ) + mWindow.ContextConfig.Version = GLv_ES1; + + Log::notice( "Starting GLES1" ); + + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 1 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 ); + } else { + Log::notice( "Starting GLES2" ); + + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 2 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 0 ); + } + } else { +#if defined( EE_GLES2 ) + Log::notice( "Starting GLES2 default" ); + + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 2 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 0 ); +#else + Log::notice( "Starting GLES1 default" ); + + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 1 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 ); +#endif + } +#else + if ( GLv_3CP == Context.Version ) { + SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2 ); + } +#endif + +#ifdef SDL3_THREADED_GLCONTEXT + if ( mWindow.ContextConfig.SharedGLContext ) { + SDL_GL_SetAttribute( SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1 ); + + mGLContextThread = SDL_GL_CreateContext( mSDLWindow ); + mGLContext = SDL_GL_CreateContext( mSDLWindow ); + } else { + mGLContext = SDL_GL_CreateContext( mSDLWindow ); + } +#else + mGLContext = SDL_GL_CreateContext( mSDLWindow ); + mWindow.ContextConfig.SharedGLContext = false; +#endif + + if ( NULL == mGLContext +#ifdef SDL3_THREADED_GLCONTEXT + || ( mWindow.ContextConfig.SharedGLContext && NULL == mGLContextThread ) +#endif + ) { + Log::error( "Unable to create context: %s", SDL_GetError() ); + + logFailureInit( "WindowSDL", getVersion() ); + + return false; + } + + int w, h; + SDL_GetWindowSizeInPixels( mSDLWindow, &w, &h ); + + if ( w > 0 && h > 0 ) { + mWindow.WindowConfig.Width = w; + mWindow.WindowConfig.Height = h; + mWindow.WindowSize = Sizei( mWindow.WindowConfig.Width, mWindow.WindowConfig.Height ); + mLastWindowedSize = mWindow.WindowSize; + } else { + Log::error( "Window failed to create!" ); + + if ( NULL != SDL_GetError() && SDL_GetError()[0] != '\0' ) { + Log::error( "SDL Error: %s", SDL_GetError() ); + } + + return false; + } + + SDL_GL_SetSwapInterval( ( mWindow.ContextConfig.VSync ? 1 : 0 ) ); + + SDL_GL_MakeCurrent( mSDLWindow, mGLContext ); + + mID = SDL_GetWindowID( mSDLWindow ); + + mWMinfo = eeNew( WMInfo, ( mSDLWindow ) ); + + if ( NULL == Renderer::existsSingleton() ) { + Renderer::createSingleton( mWindow.ContextConfig.Version ); + Renderer::instance()->init(); + if ( mWindow.ContextConfig.Multisamples > 0 ) + Renderer::instance()->multisample( true ); + } + + getMainContext(); + + setTitle( mWindow.WindowConfig.Title ); + + createView(); + + setup2D( false ); + + mWindow.Created = true; + + if ( "" != mWindow.WindowConfig.Icon ) { + setIcon( mWindow.WindowConfig.Icon ); + } + + static_cast( mClipboard )->init(); + + static_cast( mInput )->init(); + + mCursorManager->set( Cursor::SysArrow ); + + logSuccessfulInit( getVersion() ); + + return true; +} + +Uint32 WindowSDL::getWindowID() const { + return mID; +} + +void WindowSDL::makeCurrent() { + SDL_GL_MakeCurrent( mSDLWindow, mGLContext ); +} + +void WindowSDL::close() { + SDL_DestroyWindow( mSDLWindow ); + mSDLWindow = NULL; + mGLContext = NULL; + mGLContextThread = NULL; + Window::close(); +} + +void WindowSDL::setCurrent() { + makeCurrent(); +} + +bool WindowSDL::isThreadedGLContext() const { +#ifdef SDL3_THREADED_GLCONTEXT + return mWindow.ContextConfig.SharedGLContext; +#else + return false; +#endif +} + +void WindowSDL::setGLContextThread() { + mGLContextMutex.lock(); + SDL_GL_MakeCurrent( mSDLWindow, mGLContextThread ); +} + +void WindowSDL::unsetGLContextThread() { + SDL_GL_MakeCurrent( mSDLWindow, NULL ); + mGLContextMutex.unlock(); +} + +int WindowSDL::getCurrentDisplayIndex() const { + if ( !mSDLWindow ) + return 0; + SDL_DisplayID displayID = SDL_GetDisplayForWindow( mSDLWindow ); + // Map display ID to index using the display manager + auto* dispMgr = static_cast( Engine::instance()->getDisplayManager() ); + int idx = dispMgr->getDisplayIndexFromID( displayID ); + return idx >= 0 ? idx : 0; +} + +std::string WindowSDL::getVersion() { + int major = SDL_MAJOR_VERSION; + int minor = SDL_MINOR_VERSION; + int patch = SDL_MICRO_VERSION; + + return String::format( "SDL %d.%d.%d", major, minor, patch ); +} + +void WindowSDL::setGLConfig() { + if ( mWindow.ContextConfig.DepthBufferSize ) + SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, mWindow.ContextConfig.DepthBufferSize ); + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, ( mWindow.ContextConfig.DoubleBuffering ? 1 : 0 ) ); + if ( mWindow.ContextConfig.StencilBufferSize ) + SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, mWindow.ContextConfig.StencilBufferSize ); + + if ( mWindow.WindowConfig.BitsPerPixel == 16 ) { + SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 4 ); + SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 4 ); + SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 4 ); + SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 4 ); + } else { + SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 ); + } +} + +void WindowSDL::toggleFullscreen() { + Log::info( "toggleFullscreen called: %s", isWindowed() ? "is windowed" : "is fullscreen" ); + + if ( isWindowed() ) { + mWinPos = getPosition(); + mWindow.Maximized = isMaximized(); + } + + SDL_SetWindowFullscreen( mSDLWindow, !isWindowed() ? false : true ); + + BitOp::setBitFlagValue( &mWindow.WindowConfig.Style, WindowStyle::Fullscreen, + isWindowed() ? 1 : 0 ); + + getCursorManager()->reload(); + + if ( isWindowed() ) { + setPosition( mWinPos.x, mWinPos.y ); + + if ( mWindow.Maximized ) + maximize(); + } +} + +void WindowSDL::setTitle( const std::string& title ) { + if ( mWindow.WindowConfig.Title != title ) { + mWindow.WindowConfig.Title = title; + + SDL_SetWindowTitle( mSDLWindow, title.c_str() ); + } +} + +bool WindowSDL::isActive() const { + Uint64 flags = SDL_GetWindowFlags( mSDLWindow ); + return 0 != ( ( flags & SDL_WINDOW_INPUT_FOCUS ) && ( flags & SDL_WINDOW_MOUSE_FOCUS ) ); +} + +bool WindowSDL::isVisible() const { + Uint64 flags = SDL_GetWindowFlags( mSDLWindow ); + return 0 != ( !( flags & SDL_WINDOW_HIDDEN ) && !( flags & SDL_WINDOW_MINIMIZED ) ); +} + +bool WindowSDL::hasFocus() const { + Uint64 flags = SDL_GetWindowFlags( mSDLWindow ); + return 0 != ( flags & ( SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS ) ); +} + +bool WindowSDL::hasInputFocus() const { + Uint64 flags = SDL_GetWindowFlags( mSDLWindow ); + return 0 != ( flags & SDL_WINDOW_INPUT_FOCUS ); +} + +bool WindowSDL::hasMouseFocus() const { + Uint64 flags = SDL_GetWindowFlags( mSDLWindow ); + return 0 != ( flags & SDL_WINDOW_MOUSE_FOCUS ); +} + +void WindowSDL::onWindowResize( Uint32 width, Uint32 height ) { + if ( width == mWindow.WindowConfig.Width && height == mWindow.WindowConfig.Height ) + return; + + Log::debug( "onWindowResize: Width %d Height %d.", width, height ); + + mWindow.WindowConfig.Width = width; + mWindow.WindowConfig.Height = height; + mWindow.WindowSize = Sizei( width, height ); + + if ( isWindowed() ) + mLastWindowedSize = Sizei( width, height ); + + mDefaultView.reset( Rectf( 0, 0, mWindow.WindowConfig.Width, mWindow.WindowConfig.Height ) ); + + setup2D( false ); + + SDL_PumpEvents(); + + SDL_FlushEvent( SDL_EVENT_WINDOW_RESIZED ); + + mCursorManager->reload(); + + sendVideoResizeCb(); +} + +void WindowSDL::setSize( Uint32 width, Uint32 height, bool windowed ) { + if ( ( !width || !height ) ) { + width = mWindow.DesktopResolution.getWidth(); + height = mWindow.DesktopResolution.getHeight(); + } + + if ( this->isWindowed() == windowed && width == mWindow.WindowConfig.Width && + height == mWindow.WindowConfig.Height ) + return; + + Log::debug( "Switching from %s to %s. Width: %d Height %d.", + this->isWindowed() ? "windowed" : "fullscreen", + windowed ? "windowed" : "fullscreen", width, height ); + + Uint32 oldWidth = mWindow.WindowConfig.Width; + Uint32 oldHeight = mWindow.WindowConfig.Height; + + mWindow.WindowConfig.Width = width; + mWindow.WindowConfig.Height = height; + + if ( windowed ) { + mWindow.WindowSize = Sizei( width, height ); + } else { + mWindow.WindowSize = Sizei( oldWidth, oldHeight ); + } + + if ( isWindowed() && !windowed ) { + mWinPos = getPosition(); + } else { + SDL_SetWindowFullscreen( mSDLWindow, !windowed ); + } + + if ( windowed ) + mLastWindowedSize = Sizei( width, height ); + + SDL_SetWindowSize( mSDLWindow, width, height ); + + if ( isWindowed() && !windowed ) { + mWinPos = getPosition(); + + setGLConfig(); + + SDL_SetWindowFullscreen( mSDLWindow, !windowed ); + } + + if ( isWindowed() && windowed ) { + setPosition( mWinPos.x, mWinPos.y ); + } + + BitOp::setBitFlagValue( &mWindow.WindowConfig.Style, WindowStyle::Fullscreen, !windowed ); + + mDefaultView.reset( Rectf( 0, 0, width, height ) ); + + setup2D( false ); + + SDL_PumpEvents(); + + SDL_FlushEvent( SDL_EVENT_WINDOW_RESIZED ); + + mCursorManager->reload(); + + sendVideoResizeCb(); +} + +void WindowSDL::swapBuffers() { + SDL_GL_SwapWindow( mSDLWindow ); +} + +std::vector WindowSDL::getDisplayModes() const { + std::vector result; + + int displayCount = 0; + SDL_DisplayID* displays = SDL_GetDisplays( &displayCount ); + + if ( displays ) { + for ( int x = 0; x < displayCount; x++ ) { + int modeCount = 0; + SDL_DisplayMode** modesArray = SDL_GetFullscreenDisplayModes( displays[x], &modeCount ); + + if ( modesArray ) { + for ( int i = 0; i < modeCount; i++ ) { + SDL_DisplayMode* mode = modesArray[i]; + result.push_back( DisplayMode( mode->w, mode->h, (int)mode->refresh_rate, x ) ); + } + SDL_free( modesArray ); + } + } + SDL_free( displays ); + } + + return result; +} + +void WindowSDL::setGamma( Float Red, Float Green, Float Blue ) { + // SDL3 removed gamma ramp functionality +} + +eeWindowHandle WindowSDL::getWindowHandler() const { + if ( NULL != mWMinfo ) { + return mWMinfo->getWindowHandler(); + } + return 0; +} + +bool WindowSDL::setIcon( const std::string& Path ) { + int x, y, c; + + if ( !mWindow.Created ) { + if ( Image::getInfo( Path.c_str(), &x, &y, &c ) ) { + mWindow.WindowConfig.Icon = Path; + + return true; + } + + return false; + } + + Image Img( Path ); + + if ( NULL != Img.getPixelsPtr() ) { +#if EE_PLATFORM == EE_PLATFORM_WIN + if ( Img.getWidth() > 64 || Img.getHeight() > 64 ) { + Img.resize( 64, 64 ); + } +#endif + const Uint8* Ptr = Img.getPixelsPtr(); + x = Img.getWidth(); + y = Img.getHeight(); + c = Img.getChannels(); + + if ( ( x % 8 ) == 0 && ( y % 8 ) == 0 ) { + SDL_Surface* iconSurface = SDL_CreateSurface( x, y, SDL_PIXELFORMAT_RGBA32 ); + + if ( iconSurface ) { + Uint32 ssize = iconSurface->w * iconSurface->h * c; + for ( Uint32 i = 0; i < ssize; i++ ) { + ( static_cast( iconSurface->pixels ) )[i + 0] = ( Ptr )[i]; + } + + SDL_SetWindowIcon( mSDLWindow, iconSurface ); + + SDL_DestroySurface( iconSurface ); + + return true; + } + } + } + + return false; +} + +void WindowSDL::minimize() { + SDL_MinimizeWindow( mSDLWindow ); +} + +void WindowSDL::maximize() { + SDL_MaximizeWindow( mSDLWindow ); +} + +bool WindowSDL::isMaximized() const { + return SDL_GetWindowFlags( mSDLWindow ) & SDL_WINDOW_MAXIMIZED; +} + +bool WindowSDL::isMinimized() const { + return SDL_GetWindowFlags( mSDLWindow ) & SDL_WINDOW_MINIMIZED; +} + +void WindowSDL::hide() { + SDL_HideWindow( mSDLWindow ); +} + +void WindowSDL::raise() { + SDL_RaiseWindow( mSDLWindow ); +} + +void WindowSDL::restore() { + SDL_RestoreWindow( mSDLWindow ); +} + +void WindowSDL::flash( WindowFlashOperation op ) { +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN + SDL_FlashOperation sdlOp = SDL_FLASH_BRIEFLY; + if ( op == WindowFlashOperation::Cancel ) + sdlOp = SDL_FLASH_CANCEL; + else if ( op == WindowFlashOperation::UntilFocused ) + sdlOp = SDL_FLASH_UNTIL_FOCUSED; + SDL_FlashWindow( mSDLWindow, sdlOp ); +#endif +} + +void WindowSDL::show() { + SDL_ShowWindow( mSDLWindow ); +} + +void WindowSDL::setPosition( int Left, int Top ) { + SDL_SetWindowPosition( mSDLWindow, Left, Top ); +} + +Vector2i WindowSDL::getPosition() const { + Vector2i p; + + SDL_GetWindowPosition( mSDLWindow, &p.x, &p.y ); + + return p; +} + +void WindowSDL::updateDesktopResolution() const { + SDL_DisplayID displayID = SDL_GetDisplayForWindow( mSDLWindow ); + const SDL_DisplayMode* dpm = SDL_GetDesktopDisplayMode( displayID ); + if ( dpm ) { + mWindow.DesktopResolution = Sizei( dpm->w, dpm->h ); + } else { + Log::warning( "Failed to get desktop display mode for display %u", displayID ); + } +} + +const Sizei& WindowSDL::getDesktopResolution() const { + updateDesktopResolution(); + return Window::getDesktopResolution(); +} + +Rect WindowSDL::getBorderSize() const { + Rect bordersSize; + SDL_GetWindowBordersSize( mSDLWindow, &bordersSize.Top, &bordersSize.Left, &bordersSize.Bottom, + &bordersSize.Right ); + return bordersSize; +} + +Float WindowSDL::getScale() const { + int realX, realY; + int scaledX, scaledY; + SDL_GetWindowSizeInPixels( mSDLWindow, &realX, &realY ); + SDL_GetWindowSize( mSDLWindow, &scaledX, &scaledY ); + return (Float)realX / (Float)scaledX; +} + +bool WindowSDL::hasNativeMessageBox() const { + return true; +} + +Uint32 toSDLMsgBoxType( const Window::MessageBoxType& type ) { + switch ( type ) { + case Window::MessageBoxType::Error: + return SDL_MESSAGEBOX_ERROR; + case Window::MessageBoxType::Warning: + return SDL_MESSAGEBOX_WARNING; + case Window::MessageBoxType::Information: + return SDL_MESSAGEBOX_INFORMATION; + } + return SDL_MESSAGEBOX_INFORMATION; +} + +bool WindowSDL::showMessageBox( const MessageBoxType& type, const std::string& title, + const std::string& message ) { + return SDL_ShowSimpleMessageBox( toSDLMsgBoxType( type ), title.c_str(), message.c_str(), + mSDLWindow ); +} + +SDL_Window* WindowSDL::getSDLWindow() const { + return mSDLWindow; +} + +void WindowSDL::startOnScreenKeyboard() { +#if EE_PLATFORM == EE_PLATFORM_WIN + showOSK( getWindowHandler() ); +#elif defined( EE_X11_PLATFORM ) + showOSK(); +#endif +} + +void WindowSDL::stopOnScreenKeyboard() { +#if EE_PLATFORM == EE_PLATFORM_WIN + hideOSK(); +#elif defined( EE_X11_PLATFORM ) + hideOSK(); +#endif +} + +bool WindowSDL::isOnScreenKeyboardActive() const { +#if EE_PLATFORM == EE_PLATFORM_WIN + return WIN_OSK_VISIBLE; +#elif defined( EE_X11_PLATFORM ) + return ONBOARD_PID != 0; +#else + return false; +#endif +} + +void WindowSDL::startTextInput() { + if ( mWindow.WindowConfig.UseScreenKeyboard ) { + startOnScreenKeyboard(); + } else { + SDL_StartTextInput( mSDLWindow ); + } +} + +bool WindowSDL::isTextInputActive() const { + if ( mWindow.WindowConfig.UseScreenKeyboard ) + return isOnScreenKeyboardActive(); + return SDL_TextInputActive( mSDLWindow ); +} + +void WindowSDL::stopTextInput() { + if ( mWindow.WindowConfig.UseScreenKeyboard ) { + stopOnScreenKeyboard(); + } else { + SDL_StopTextInput( mSDLWindow ); + } +} + +void WindowSDL::setTextInputRect( const Rect& rect ) { + SDL_Rect r; + r.x = rect.Left; + r.y = rect.Top; + r.w = rect.getSize().getWidth(); + r.h = rect.getSize().getHeight(); + + SDL_SetTextInputArea( mSDLWindow, &r, 0 ); +} + +void WindowSDL::clearComposition() { + SDL_ClearComposition( mSDLWindow ); +} + +bool WindowSDL::hasScreenKeyboardSupport() const { + return SDL_HasScreenKeyboardSupport(); +} + +bool WindowSDL::isScreenKeyboardShown() const { + return SDL_ScreenKeyboardShown( mSDLWindow ); +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/windowsdl3.hpp b/src/eepp/window/backend/SDL3/windowsdl3.hpp new file mode 100644 index 000000000..980247658 --- /dev/null +++ b/src/eepp/window/backend/SDL3/windowsdl3.hpp @@ -0,0 +1,145 @@ +#ifndef EE_WINDOWCWINDOWSDL3_HPP +#define EE_WINDOWCWINDOWSDL3_HPP + +#include +#include + +#ifdef EE_BACKEND_SDL3 + +#include +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API WindowSDL : public Window { + public: + WindowSDL( WindowSettings Settings, ContextSettings Context ); + + virtual ~WindowSDL(); + + bool create( WindowSettings Settings, ContextSettings Context ); + + Uint32 getWindowID() const; + + void makeCurrent(); + + void close(); + + void setCurrent(); + + void toggleFullscreen(); + + void setTitle( const std::string& title ); + + bool setIcon( const std::string& Path ); + + bool isActive() const; + + bool isVisible() const; + + bool hasFocus() const; + + bool hasInputFocus() const; + + bool hasMouseFocus() const; + + void setSize( Uint32 width, Uint32 height, bool windowed ); + + std::vector getDisplayModes() const; + + void setGamma( Float Red, Float Green, Float Blue ); + + eeWindowHandle getWindowHandler() const; + + virtual void minimize(); + + virtual void maximize(); + + virtual bool isMaximized() const; + + virtual bool isMinimized() const; + + virtual void hide(); + + virtual void raise(); + + virtual void restore(); + + virtual void flash( WindowFlashOperation op ); + + virtual void show(); + + virtual void setPosition( int Left, int Top ); + + virtual Vector2i getPosition() const; + + const Sizei& getDesktopResolution() const; + + virtual Rect getBorderSize() const; + + virtual Float getScale() const; + + virtual bool hasNativeMessageBox() const; + + virtual bool showMessageBox( const MessageBoxType& type, const std::string& title, + const std::string& message ); + + SDL_Window* getSDLWindow() const; + + void startOnScreenKeyboard(); + + void stopOnScreenKeyboard(); + + bool isOnScreenKeyboardActive() const; + + void startTextInput(); + + bool isTextInputActive() const; + + void stopTextInput(); + + void setTextInputRect( const Rect& rect ); + + void clearComposition(); + + bool hasScreenKeyboardSupport() const; + + bool isScreenKeyboardShown() const; + + bool isThreadedGLContext() const; + + void setGLContextThread(); + + void unsetGLContextThread(); + + int getCurrentDisplayIndex() const; + + protected: + friend class ClipboardSDL; + + SDL_Window* mSDLWindow{ nullptr }; + SDL_GLContext mGLContext; + SDL_GLContext mGLContextThread; + Mutex mGLContextMutex; + Uint32 mID{ 0 }; + + WMInfo* mWMinfo; + + Vector2i mWinPos; + + void swapBuffers(); + + void setGLConfig(); + + std::string getVersion(); + + void updateDesktopResolution() const; + + void onWindowResize( Uint32 width, Uint32 height ); +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif + +#endif diff --git a/src/eepp/window/backend/SDL3/wminfo.cpp b/src/eepp/window/backend/SDL3/wminfo.cpp new file mode 100644 index 000000000..7f4e60d39 --- /dev/null +++ b/src/eepp/window/backend/SDL3/wminfo.cpp @@ -0,0 +1,34 @@ +#include + +#ifdef EE_BACKEND_SDL3 + +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +WMInfo::WMInfo( SDL_Window* win ) : mProps( SDL_GetWindowProperties( win ) ) {} + +WMInfo::~WMInfo() {} + +#if defined( EE_X11_PLATFORM ) +X11Window WMInfo::getWindow() const { + return 0; +} +#endif + +eeWindowHandle WMInfo::getWindowHandler() const { +#if EE_PLATFORM == EE_PLATFORM_WIN + return (eeWindowHandle)SDL_GetPointerProperty( mProps, SDL_PROP_WINDOW_WIN32_HWND_POINTER, 0 ); +#elif defined( EE_X11_PLATFORM ) + return (eeWindowHandle)SDL_GetNumberProperty( mProps, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0 ); +#elif EE_PLATFORM == EE_PLATFORM_MACOS + return (eeWindowHandle)SDL_GetPointerProperty( mProps, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, + 0 ); +#else + return 0; +#endif +} + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/backend/SDL3/wminfo.hpp b/src/eepp/window/backend/SDL3/wminfo.hpp new file mode 100644 index 000000000..b557b43a7 --- /dev/null +++ b/src/eepp/window/backend/SDL3/wminfo.hpp @@ -0,0 +1,27 @@ +#ifndef EE_BACKEND_SDL3_WMINFO_HPP +#define EE_BACKEND_SDL3_WMINFO_HPP + +#include +#include + +namespace EE { namespace Window { namespace Backend { namespace SDL3 { + +class EE_API WMInfo { + public: + WMInfo( SDL_Window* win ); + + ~WMInfo(); + +#if defined( EE_X11_PLATFORM ) + X11Window getWindow() const; +#endif + + eeWindowHandle getWindowHandler() const; + + protected: + SDL_PropertiesID mProps; +}; + +}}}} // namespace EE::Window::Backend::SDL3 + +#endif diff --git a/src/eepp/window/engine.cpp b/src/eepp/window/engine.cpp index 2300fdfd3..5477ef8d2 100644 --- a/src/eepp/window/engine.cpp +++ b/src/eepp/window/engine.cpp @@ -24,6 +24,10 @@ #include #include #include +#if defined(EE_SDL_VERSION_3) +#include +#include +#endif #include #if EE_PLATFORM == EE_PLATFORM_ANDROID @@ -31,10 +35,13 @@ #endif #define BACKEND_SDL2 1 +#define BACKEND_SDL3 2 #ifndef DEFAULT_BACKEND -#if defined( EE_BACKEND_SDL2 ) +#if defined( EE_BACKEND_SDL3 ) +#define DEFAULT_BACKEND BACKEND_SDL3 +#elif defined( EE_BACKEND_SDL2 ) #define DEFAULT_BACKEND BACKEND_SDL2 #endif @@ -150,10 +157,35 @@ EE::Window::Window* Engine::createSDL2Window( const WindowSettings& Settings, #endif } +#ifdef EE_BACKEND_SDL3 +Backend::WindowBackendLibrary* Engine::createSDL3Backend( const WindowSettings& ) { +#if defined( EE_SDL_VERSION_3 ) + return eeNew( Backend::SDL3::WindowBackendSDL3, () ); +#else + return NULL; +#endif +} + +EE::Window::Window* Engine::createSDL3Window( const WindowSettings& Settings, + const ContextSettings& Context ) { +#if defined( EE_SDL_VERSION_3 ) + if ( NULL == mBackend ) { + mBackend = createSDL3Backend( Settings ); + } + + return eeNew( Backend::SDL3::WindowSDL, ( Settings, Context ) ); +#else + return NULL; +#endif +} +#endif + EE::Window::Window* Engine::createDefaultWindow( const WindowSettings& Settings, const ContextSettings& Context ) { #if DEFAULT_BACKEND == BACKEND_SDL2 return createSDL2Window( Settings, Context ); +#elif DEFAULT_BACKEND == BACKEND_SDL3 + return createSDL3Window( Settings, Context ); #else return NULL; #endif @@ -251,6 +283,8 @@ bool Engine::isRunning() const { WindowBackend Engine::getDefaultBackend() const { #if DEFAULT_BACKEND == BACKEND_SDL2 return WindowBackend::SDL2; +#elif DEFAULT_BACKEND == BACKEND_SDL3 + return WindowBackend::SDL3; #else return WindowBackend::Default; #endif @@ -293,6 +327,8 @@ WindowSettings Engine::createWindowSettings( IniFile* ini, std::string iniKeyNam if ( "sdl2" == backend ) winBackend = WindowBackend::SDL2; + else if ( "sdl3" == backend ) + winBackend = WindowBackend::SDL3; Uint32 Style = WindowStyle::Titlebar; @@ -382,6 +418,8 @@ PlatformHelper* Engine::getPlatformHelper() { if ( NULL == mPlatformHelper ) { #if DEFAULT_BACKEND == BACKEND_SDL2 mPlatformHelper = eeNew( Backend::SDL2::PlatformHelperSDL2, () ); +#elif DEFAULT_BACKEND == BACKEND_SDL3 + mPlatformHelper = eeNew( Backend::SDL3::PlatformHelperSDL3, () ); #endif } @@ -392,6 +430,8 @@ DisplayManager* Engine::getDisplayManager() { if ( NULL == mDisplayManager ) { #if DEFAULT_BACKEND == BACKEND_SDL2 mDisplayManager = eeNew( Backend::SDL2::DisplayManagerSDL2, () ); +#elif DEFAULT_BACKEND == BACKEND_SDL3 + mDisplayManager = eeNew( Backend::SDL3::DisplayManagerSDL3, () ); #endif } diff --git a/src/eepp/window/window.cpp b/src/eepp/window/window.cpp index f1d262caf..86dfb3bba 100644 --- a/src/eepp/window/window.cpp +++ b/src/eepp/window/window.cpp @@ -348,9 +348,17 @@ void Window::close() { void Window::setFrameRateLimit( Uint32 FrameRateLimit ) { if ( FrameRateLimit == ContextSettings::FrameRateLimitScreenRefreshRate ) { - Display* currentDisplay = Engine::instance()->getDisplayManager()->getDisplayIndex( - isOpen() ? getCurrentDisplayIndex() : 0 ); - FrameRateLimit = currentDisplay->getRefreshRate(); + Display* currentDisplay = nullptr; + if ( Engine::existsSingleton() && Engine::instance()->getDisplayManager() ) { + currentDisplay = Engine::instance()->getDisplayManager()->getDisplayIndex( + getCurrentDisplayIndex() ); + } + if ( currentDisplay ) { + FrameRateLimit = currentDisplay->getRefreshRate(); + } else { + // Fallback to 60 FPS if display info not available + FrameRateLimit = 60; + } } mFrameData.FPS.Limit = (Float)FrameRateLimit; }