diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 48593b3fa..e861bd603 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -20,6 +20,7 @@ namespace Nullable Fullscreen; Nullable Size; bool CursorVisible = true; + CursorType CachedCursorType = CursorType::Default; CursorLockMode CursorLock = CursorLockMode::None; CursorLockMode PendingCursorLock = CursorLockMode::None; bool LastGameViewportFocus = false; @@ -107,10 +108,17 @@ void Screen::SetCursorVisible(const bool value) #else const auto win = Engine::MainWindow; #endif + if (!value && win) + { + // Cache cursor used before hiding it to restore it later (eg. if game uses image cursor and hides it for a while) + CachedCursorType = win->GetCursor(); + if (CachedCursorType == CursorType::Hidden) + CachedCursorType = CursorType::Default; + } if (win && Engine::HasGameViewportFocus()) - win->SetCursor(value ? CursorType::Default : CursorType::Hidden); + win->SetCursor(value ? CachedCursorType : CursorType::Hidden); else if (win) - win->SetCursor(CursorType::Default); + win->SetCursor(CachedCursorType); CursorVisible = value; } diff --git a/Source/Engine/Platform/Base/Enums.h b/Source/Engine/Platform/Base/Enums.h index 4a903ad19..df83ec0cb 100644 --- a/Source/Engine/Platform/Base/Enums.h +++ b/Source/Engine/Platform/Base/Enums.h @@ -36,7 +36,7 @@ API_ENUM() enum class ClosingReason API_ENUM() enum class CursorType { /// - /// The default. + /// The default cursor, usually an arrow. /// Default = 0, @@ -56,7 +56,7 @@ API_ENUM() enum class CursorType Help, /// - /// The I beam. + /// The I-beam for text selection. /// IBeam, @@ -100,6 +100,11 @@ API_ENUM() enum class CursorType /// Hidden, + /// + /// The custom cursor image. Loaded manually via Window::LoadCursorImage. + /// + Image, + MAX }; diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index f11abb398..f7f2a4294 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -23,7 +23,7 @@ API_INJECT_CODE(cpp, "#include \"Engine/Platform/Window.h\""); /// /// Native platform window object. /// -API_CLASS(NoSpawn, NoConstructor, Sealed, Name="Window") +API_CLASS(NoSpawn, NoConstructor, Sealed, Name="Window", Tag="NativeInvokeUseName") class FLAXENGINE_API WindowBase : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(WindowBase); @@ -31,16 +31,17 @@ class FLAXENGINE_API WindowBase : public ScriptingObject protected: bool _visible, _minimized, _maximized, _isClosing, _showAfterFirstPaint, _focused; GPUSwapChain* _swapChain; + void* _cursorImage = nullptr; CreateWindowSettings _settings; String _title; CursorType _cursor; Float2 _clientSize; - int _dpi; + int32 _dpi; float _dpiScale; Float2 _trackingMouseOffset; - bool _isUsingMouseOffset = false; Rectangle _mouseOffsetScreenSize; + bool _isUsingMouseOffset = false; bool _isTrackingMouse = false; bool _isHorizontalFlippingMouse = false; bool _isVerticalFlippingMouse = false; @@ -546,6 +547,52 @@ public: _cursor = type; } + /// + /// Gets the mouse cursor image handle. + /// + API_PROPERTY() FORCE_INLINE void* GetCursorImage() const + { + return _cursorImage; + } + + /// + /// Sets the mouse cursor image handle. + /// + /// The cursor image. + API_PROPERTY() virtual void SetCursorImage(void* image) + { + _cursorImage = image; + } + + /// + /// Loads a cursor file as a cursor image. Returns a native cursor handle that can be used with `SetCursorImage`. Support depends on platform (eg. Windows uses .cur/.ani files). + /// + /// Path to the cursor file (absolute or relative). + /// Loaded cursor image handle, or null if failed. + API_FUNCTION() static void* LoadCursorImage(const StringView& path) + { + return nullptr; + } + + /// + /// Loads a texture as a cursor image. Returns a native cursor handle that can be used with `SetCursorImage`. Support depends on platform. + /// + /// Texture data with a cursor image. + /// The texture coordinate of a cursor's hot spot that defines the exact 'clickable' point. + /// Loaded cursor image handle, or null if failed. + API_FUNCTION() static void* LoadCursorImage(const TextureData& image, const Int2& hotSpot = Int2::Zero) + { + return nullptr; + } + + /// + /// Destroys a cursor image loaded with `LoadCursorImage`. Should be called to release native resources after the cursor image is not needed anymore. + /// + /// The cursor image. + API_FUNCTION() static void DestroyCursorImage(void* image) + { + } + /// /// Sets the window icon. /// diff --git a/Source/Engine/Platform/GDK/GDKWindow.cpp b/Source/Engine/Platform/GDK/GDKWindow.cpp index c8be2df3f..d75063661 100644 --- a/Source/Engine/Platform/GDK/GDKWindow.cpp +++ b/Source/Engine/Platform/GDK/GDKWindow.cpp @@ -230,64 +230,32 @@ void GDKWindow::UpdateCursor() const return; } - int32 index = 0; - switch (_cursor) + const LPCWSTR cursors[] = { - case CursorType::Default: - break; - case CursorType::Cross: - index = 1; - break; - case CursorType::Hand: - index = 2; - break; - case CursorType::Help: - index = 3; - break; - case CursorType::IBeam: - index = 4; - break; - case CursorType::No: - index = 5; - break; - case CursorType::Wait: - index = 11; - break; - case CursorType::SizeAll: - index = 6; - break; - case CursorType::SizeNESW: - index = 7; - break; - case CursorType::SizeNS: - index = 8; - break; - case CursorType::SizeNWSE: - index = 9; - break; - case CursorType::SizeWE: - index = 10; - break; - } - - static const LPCWSTR cursors[] = - { - IDC_ARROW, - IDC_CROSS, - IDC_HAND, - IDC_HELP, - IDC_IBEAM, - IDC_NO, - IDC_SIZEALL, - IDC_SIZENESW, - IDC_SIZENS, - IDC_SIZENWSE, - IDC_SIZEWE, - IDC_WAIT, + IDC_ARROW, // Default + IDC_CROSS, // Cross + IDC_HAND, // Hand + IDC_HELP, // Help + IDC_IBEAM, // IBeam + IDC_NO, // No + IDC_WAIT, // Wait + IDC_SIZEALL, // SizeAll + IDC_SIZENESW, // SizeNESW + IDC_SIZENS, // SizeNS + IDC_SIZENWSE, // SizeNWSE + IDC_SIZEWE, // SizeWE }; + static_assert(ARRAY_COUNT(cursors) + 2 == (int32)CursorType::MAX, "Invalid cursors count."); + + HCURSOR cursor; + if (_cursor == CursorType::Image) + { + LOG(Error, "GDK doesn't support hardware image cursors"); + cursor = NULL; + } + else + cursor = LoadCursorW(nullptr, cursors[(int32)_cursor]); - ASSERT(index >= 0 && index < ARRAY_COUNT(cursors)); - const HCURSOR cursor = LoadCursorW(nullptr, cursors[index]); ::SetCursor(cursor); } diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 9e43d778b..ce9ebbd56 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -846,7 +846,7 @@ void LinuxWindow::SetCursor(CursorType type) WindowBase::SetCursor(type); LINUX_WINDOW_PROLOG; - if (!display) + if (!display || type == CursorType::Image) return; X11::XDefineCursor(display, window, Cursors[(int32)type]); } diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index b7014681a..085e08afb 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -1197,6 +1197,8 @@ void MacWindow::SetCursor(CursorType type) case CursorType::Hidden: [NSCursor hide]; return; + case CursorType::Image: + // TODO: custom cursor support default: cursor = [NSCursor arrowCursor]; break; diff --git a/Source/Engine/Platform/SDL/SDLWindow.cpp b/Source/Engine/Platform/SDL/SDLWindow.cpp index f284ed5bb..df8603b61 100644 --- a/Source/Engine/Platform/SDL/SDLWindow.cpp +++ b/Source/Engine/Platform/SDL/SDLWindow.cpp @@ -995,50 +995,42 @@ void SDLWindow::UpdateCursor() //if (_isTrackingMouse) // Input::Mouse->SetRelativeMode(false, this); - int32 index = SDL_SYSTEM_CURSOR_DEFAULT; - switch (_cursor) + const SDL_SystemCursor cursors[] = { - case CursorType::Cross: - index = SDL_SYSTEM_CURSOR_CROSSHAIR; - break; - case CursorType::Hand: - index = SDL_SYSTEM_CURSOR_POINTER; - break; - case CursorType::Help: - //index = SDL_SYSTEM_CURSOR_DEFAULT; - break; - case CursorType::IBeam: - index = SDL_SYSTEM_CURSOR_TEXT; - break; - case CursorType::No: - index = SDL_SYSTEM_CURSOR_NOT_ALLOWED; - break; - case CursorType::Wait: - index = SDL_SYSTEM_CURSOR_WAIT; - break; - case CursorType::SizeAll: - index = SDL_SYSTEM_CURSOR_MOVE; - break; - case CursorType::SizeNESW: - index = SDL_SYSTEM_CURSOR_NESW_RESIZE; - break; - case CursorType::SizeNS: - index = SDL_SYSTEM_CURSOR_NS_RESIZE; - break; - case CursorType::SizeNWSE: - index = SDL_SYSTEM_CURSOR_NWSE_RESIZE; - break; - case CursorType::SizeWE: - index = SDL_SYSTEM_CURSOR_EW_RESIZE; - break; - case CursorType::Default: - default: - break; - } + SDL_SYSTEM_CURSOR_DEFAULT, // Default + SDL_SYSTEM_CURSOR_CROSSHAIR, // Cross + SDL_SYSTEM_CURSOR_POINTER, // Hand + SDL_SYSTEM_CURSOR_DEFAULT, // Help + SDL_SYSTEM_CURSOR_TEXT, // IBeam + SDL_SYSTEM_CURSOR_NOT_ALLOWED, // No + SDL_SYSTEM_CURSOR_WAIT, // Wait + SDL_SYSTEM_CURSOR_MOVE, // SizeAll + SDL_SYSTEM_CURSOR_NESW_RESIZE, // SizeNESW + SDL_SYSTEM_CURSOR_NS_RESIZE, // SizeNS + SDL_SYSTEM_CURSOR_NWSE_RESIZE, // SizeNWSE + SDL_SYSTEM_CURSOR_EW_RESIZE, // SizeWE + }; + static_assert(ARRAY_COUNT(cursors) + 2 == (int32)CursorType::MAX, "Invalid cursors count."); - if (SDLImpl::Cursors[index] == nullptr) - SDLImpl::Cursors[index] = SDL_CreateSystemCursor(static_cast(index)); - SDL_SetCursor(SDLImpl::Cursors[index]); + SDL_Cursor* cursor; + if (_cursor == CursorType::Image) + { + static bool Once = true; + if (!_cursorImage && Once) + { + Once = false; + LOG(Error, "Missing cursor image to set."); + } + cursor = (SDL_Cursor*)_cursorImage; + } + else + { + int32 index = cursors[(int32)_cursor]; + if (SDLImpl::Cursors[index] == nullptr) + SDLImpl::Cursors[index] = SDL_CreateSystemCursor(static_cast(index)); + cursor = SDLImpl::Cursors[index]; + } + SDL_SetCursor(cursor); } void SDLWindow::SetIcon(TextureData& icon) @@ -1046,10 +1038,42 @@ void SDLWindow::SetIcon(TextureData& icon) Array colorData; icon.GetPixels(colorData); SDL_Surface* surface = SDL_CreateSurfaceFrom(icon.Width, icon.Height, SDL_PIXELFORMAT_ABGR8888, colorData.Get(), sizeof(Color32) * icon.Width); - SDL_SetWindowIcon(_window, surface); SDL_free(surface); } +void SDLWindow::SetCursorImage(void* image) +{ + WindowBase::SetCursorImage(image); + + if (_cursor == CursorType::Image) + UpdateCursor(); +} + +void* SDLWindow::LoadCursorImage(const StringView& path) +{ + return nullptr; +} + +void* SDLWindow::LoadCursorImage(const TextureData& image, const Int2& hotSpot) +{ + Array pixels; + if (image.GetPixels(pixels)) + { + LOG(Error, "Invalid cursor texture"); + return nullptr; + } + SDL_Surface* surface = SDL_CreateSurfaceFrom(image.Width, image.Height, SDL_PIXELFORMAT_ABGR8888, pixels.Get(), sizeof(Color32) * image.Width); + SDL_Cursor* result = SDL_CreateColorCursor(surface, hotSpot.X, hotSpot.Y); + SDL_free(surface); + return result; +} + +void SDLWindow::DestroyCursorImage(void* image) +{ + if (image) + SDL_DestroyCursor((SDL_Cursor*)image); +} + #endif diff --git a/Source/Engine/Platform/SDL/SDLWindow.h b/Source/Engine/Platform/SDL/SDLWindow.h index a84ec24c9..0b24fb154 100644 --- a/Source/Engine/Platform/SDL/SDLWindow.h +++ b/Source/Engine/Platform/SDL/SDLWindow.h @@ -108,6 +108,10 @@ public: void SetMousePosition(const Float2& position) const override; void SetCursor(CursorType type) override; void SetIcon(TextureData& icon) override; + void SetCursorImage(void* image) override; + static void* LoadCursorImage(const StringView& path); + static void* LoadCursorImage(const TextureData& image, const Int2& hotSpot = Int2::Zero); + static void DestroyCursorImage(void* image); #if USE_EDITOR && PLATFORM_WINDOWS // [IUnknown] diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 3a1cf0dd3..6fa64ab24 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -7,9 +7,11 @@ #include "WindowsInput.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Math/Color32.h" #include "Engine/Graphics/GPUSwapChain.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Textures/TextureData.h" #include "../Win32/IncludeWindowsHeaders.h" #include #if USE_EDITOR @@ -696,6 +698,71 @@ void WindowsWindow::SetCursor(CursorType type) UpdateCursor(); } +void WindowsWindow::SetCursorImage(void* image) +{ + // Base + WindowBase::SetCursorImage(image); + + if (_cursor == CursorType::Image) + UpdateCursor(); +} + +void* WindowsWindow::LoadCursorImage(const StringView& path) +{ + return ::LoadCursorFromFileW(path.GetText()); +} + +// From wingdi.h +WIN_API HBITMAP WIN_API_CALLCONV CreateBitmap(int nWidth, int nHeight, UINT nPlanes, UINT nBitCount, CONST VOID* lpBits); +WIN_API BOOL WIN_API_CALLCONV DeleteObject(HGDIOBJ ho); +#pragma comment(lib, "Gdi32.lib") + +void* WindowsWindow::LoadCursorImage(const TextureData& image, const Int2& hotSpot) +{ + // Get image pixels + Array pixels; + if (image.GetPixels(pixels)) + { + LOG(Error, "Invalid cursor texture"); + return nullptr; + } + + // RGBA -> BGRA + for (int32 y = 0; y < image.Height; y++) + { + for (int32 x = 0; x < image.Width; x++) + { + auto& color = *(pixels.Get() + y * image.Width + x); + color = Color32(color.B, color.G, color.R, color.A); + } + } + + // Initialize a dummy mask + Array pixelsMask; + pixelsMask.AddUninitialized(pixels.Count()); + Platform::MemorySet(pixelsMask.Get(), pixelsMask.Count(), 255); + + // Create cursor from the image + HBITMAP colorBitmap = ::CreateBitmap(image.Width, image.Height, 1, 32, pixels.Get()); + HBITMAP maskBitmap = ::CreateBitmap(image.Width, image.Height, 1, 8, pixelsMask.Get()); + ICONINFO iconInfo = {}; + iconInfo.xHotspot = hotSpot.X; + iconInfo.yHotspot = hotSpot.Y; + iconInfo.hbmColor = colorBitmap; + iconInfo.hbmMask = maskBitmap; + HCURSOR result = ::CreateIconIndirect(&iconInfo); + ::DeleteObject(colorBitmap); + ::DeleteObject(maskBitmap); + return result; +} + +void WindowsWindow::DestroyCursorImage(void* image) +{ + if (!image) + return; + ::DestroyCursor((HCURSOR)image); +} + void WindowsWindow::CheckForWindowResize() { // Skip for minimized window (GetClientRect for minimized window returns 0) @@ -763,76 +830,49 @@ void WindowsWindow::UpdateCursor() else if (_lastCursorHidden) { _lastCursorHidden = false; - while(::ShowCursor(TRUE) < 0) + while (::ShowCursor(TRUE) < 0) { if (_cursorHiddenSafetyCount >= 100) { LOG(Warning, "Cursor has failed to show."); break; } - _cursorHiddenSafetyCount += 1; + _cursorHiddenSafetyCount++; } _cursorHiddenSafetyCount = 0; } - int32 index = 0; - switch (_cursor) + const LPCWSTR cursors[] = { - case CursorType::Default: - break; - case CursorType::Cross: - index = 1; - break; - case CursorType::Hand: - index = 2; - break; - case CursorType::Help: - index = 3; - break; - case CursorType::IBeam: - index = 4; - break; - case CursorType::No: - index = 5; - break; - case CursorType::Wait: - index = 11; - break; - case CursorType::SizeAll: - index = 6; - break; - case CursorType::SizeNESW: - index = 7; - break; - case CursorType::SizeNS: - index = 8; - break; - case CursorType::SizeNWSE: - index = 9; - break; - case CursorType::SizeWE: - index = 10; - break; - } - - static const LPCWSTR cursors[] = - { - IDC_ARROW, - IDC_CROSS, - IDC_HAND, - IDC_HELP, - IDC_IBEAM, - IDC_NO, - IDC_SIZEALL, - IDC_SIZENESW, - IDC_SIZENS, - IDC_SIZENWSE, - IDC_SIZEWE, - IDC_WAIT, + IDC_ARROW, // Default + IDC_CROSS, // Cross + IDC_HAND, // Hand + IDC_HELP, // Help + IDC_IBEAM, // IBeam + IDC_NO, // No + IDC_WAIT, // Wait + IDC_SIZEALL, // SizeAll + IDC_SIZENESW, // SizeNESW + IDC_SIZENS, // SizeNS + IDC_SIZENWSE, // SizeNWSE + IDC_SIZEWE, // SizeWE }; + static_assert(ARRAY_COUNT(cursors) + 2 == (int32)CursorType::MAX, "Invalid cursors count."); + + HCURSOR cursor; + if (_cursor == CursorType::Image) + { + static bool Once = true; + if (!_cursorImage && Once) + { + Once = false; + LOG(Error, "Missing cursor image to set."); + } + cursor = (HCURSOR)_cursorImage; + } + else + cursor = LoadCursorW(nullptr, cursors[(int32)_cursor]); - ASSERT(index >= 0 && index < ARRAY_COUNT(cursors)); - const HCURSOR cursor = LoadCursorW(nullptr, cursors[index]); ::SetCursor(cursor); } diff --git a/Source/Engine/Platform/Windows/WindowsWindow.h b/Source/Engine/Platform/Windows/WindowsWindow.h index 7055f641e..a7b55f242 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.h +++ b/Source/Engine/Platform/Windows/WindowsWindow.h @@ -19,7 +19,6 @@ class FLAXENGINE_API WindowsWindow : public WindowBase friend WindowsPlatform; private: - Windows::HWND _handle; #if USE_EDITOR Windows::ULONG _refCount; @@ -29,16 +28,15 @@ private: bool _trackingMouse = false; bool _clipCursorSet = false; bool _lastCursorHidden = false; - int _cursorHiddenSafetyCount = 0; - float _opacity = 1.0f; bool _isDuringMaximize = false; + int32 _cursorHiddenSafetyCount = 0; + float _opacity = 1.0f; Windows::HANDLE _monitor = nullptr; Windows::LONG _clipCursorRect[4]; int32 _regionWidth = 0, _regionHeight = 0; Float2 _minimizedScreenPosition = Float2::Zero; public: - /// /// Initializes a new instance of the class. /// @@ -51,7 +49,6 @@ public: ~WindowsWindow(); public: - /// /// Gets the window handle. /// @@ -80,7 +77,6 @@ public: void GetScreenInfo(int32& x, int32& y, int32& width, int32& height) const; public: - /// /// The Windows messages procedure. /// @@ -91,13 +87,11 @@ public: Windows::LRESULT WndProc(Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam); private: - void CheckForWindowResize(); void UpdateCursor(); void UpdateRegion(); public: - // [WindowBase] void* GetNativePtr() const override; void Show() override; @@ -130,9 +124,12 @@ public: void StartClippingCursor(const Rectangle& bounds) override; void EndClippingCursor() override; void SetCursor(CursorType type) override; + void SetCursorImage(void* image) override; + static void* LoadCursorImage(const StringView& path); + static void* LoadCursorImage(const TextureData& image, const Int2& hotSpot = Int2::Zero); + static void DestroyCursorImage(void* image); #if USE_EDITOR - // [IUnknown] Windows::HRESULT __stdcall QueryInterface(const Windows::IID& id, void** ppvObject) override; Windows::ULONG __stdcall AddRef() override; @@ -143,7 +140,6 @@ public: Windows::HRESULT __stdcall DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override; Windows::HRESULT __stdcall DragLeave() override; Windows::HRESULT __stdcall Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) override; - #endif };