#include "hwcursor.hpp" #include #include #if SDL_VERSION_ATLEAST(2, 0, 0) #include #include #include #endif #include "DiabloUI/diabloui.h" #include "appfat.h" #include "cursor.h" #include "engine/clx_sprite.hpp" #include "engine/render/clx_render.hpp" #include "engine/surface.hpp" #include "utils/display.h" #include "utils/sdl_bilinear_scale.hpp" #include "utils/sdl_wrap.h" namespace devilution { namespace { CursorInfo CurrentCursorInfo; #if SDL_VERSION_ATLEAST(2, 0, 0) SDLCursorUniquePtr CurrentCursor; enum class HotpointPosition : uint8_t { TopLeft, Center, }; Size ScaledSize(Size size) { if (renderer != nullptr) { float scaleX; float scaleY; SDL_RenderGetScale(renderer, &scaleX, &scaleY); size.width = static_cast(size.width * scaleX); size.height = static_cast(size.height * scaleY); } return size; } bool IsCursorSizeAllowed(Size size) { if (*GetOptions().Graphics.hardwareCursorMaxSize <= 0) return true; size = ScaledSize(size); return size.width <= *GetOptions().Graphics.hardwareCursorMaxSize && size.height <= *GetOptions().Graphics.hardwareCursorMaxSize; } Point GetHotpointPosition(const SDL_Surface &surface, HotpointPosition position) { switch (position) { case HotpointPosition::TopLeft: return { 0, 0 }; case HotpointPosition::Center: return { surface.w / 2, surface.h / 2 }; } app_fatal("Unhandled enum value"); } bool ShouldUseBilinearScaling() { return *GetOptions().Graphics.scaleQuality != ScalingQuality::NearestPixel; } bool SetHardwareCursorFromSurface(SDL_Surface *surface, HotpointPosition hotpointPosition) { SDLCursorUniquePtr newCursor; const Size size { surface->w, surface->h }; const Size scaledSize = ScaledSize(size); if (size == scaledSize) { #if LOG_HWCURSOR Log("hwcursor: SetHardwareCursorFromSurface {}x{}", size.width, size.height); #endif const Point hotpoint = GetHotpointPosition(*surface, hotpointPosition); newCursor = SDLCursorUniquePtr { SDL_CreateColorCursor(surface, hotpoint.x, hotpoint.y) }; } else { // SDL does not support BlitScaled from 8-bit to RGBA. SDLSurfaceUniquePtr converted { SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888, 0) }; SDLSurfaceUniquePtr scaledSurface = SDLWrap::CreateRGBSurfaceWithFormat(0, scaledSize.width, scaledSize.height, 32, SDL_PIXELFORMAT_ARGB8888); if (ShouldUseBilinearScaling()) { #if LOG_HWCURSOR Log("hwcursor: SetHardwareCursorFromSurface {}x{} scaled to {}x{} using bilinear scaling", size.width, size.height, scaledSize.width, scaledSize.height); #endif BilinearScale32(converted.get(), scaledSurface.get()); } else { #if LOG_HWCURSOR Log("hwcursor: SetHardwareCursorFromSurface {}x{} scaled to {}x{} using nearest neighbour scaling", size.width, size.height, scaledSize.width, scaledSize.height); #endif SDL_BlitScaled(converted.get(), nullptr, scaledSurface.get(), nullptr); } const Point hotpoint = GetHotpointPosition(*scaledSurface, hotpointPosition); newCursor = SDLCursorUniquePtr { SDL_CreateColorCursor(scaledSurface.get(), hotpoint.x, hotpoint.y) }; } if (newCursor == nullptr) { LogError("SDL_CreateColorCursor: {}", SDL_GetError()); SDL_ClearError(); return false; } SDL_SetCursor(newCursor.get()); CurrentCursor = std::move(newCursor); return true; } bool SetHardwareCursorFromClxSprite(ClxSprite sprite, HotpointPosition hotpointPosition) { OwnedSurface surface { sprite.width(), sprite.height() }; SDL_SetSurfacePalette(surface.surface, Palette.get()); SDL_SetColorKey(surface.surface, SDL_TRUE, 0); RenderClxSprite(surface, sprite, { 0, 0 }); return SetHardwareCursorFromSurface(surface.surface, hotpointPosition); } bool SetHardwareCursorFromSprite(int pcurs) { const bool isItem = !MyPlayer->HoldItem.isEmpty(); if (isItem && !*GetOptions().Graphics.hardwareCursorForItems) return false; const int outlineWidth = isItem ? 1 : 0; auto size = GetInvItemSize(pcurs); size.width += 2 * outlineWidth; size.height += 2 * outlineWidth; if (!IsCursorSizeAllowed(size)) return false; OwnedSurface out { size }; SDL_SetSurfacePalette(out.surface, Palette.get()); // Transparent color must not be used in the sprite itself. // Colors 1-127 are outside of the UI palette so are safe to use. constexpr std::uint8_t TransparentColor = 1; SDL_FillRect(out.surface, nullptr, TransparentColor); SDL_SetColorKey(out.surface, 1, TransparentColor); DrawSoftwareCursor(out, { outlineWidth, size.height - outlineWidth - 1 }, pcurs); const bool result = SetHardwareCursorFromSurface( out.surface, isItem ? HotpointPosition::Center : HotpointPosition::TopLeft); return result; } #endif } // namespace CursorInfo &GetCurrentCursorInfo() { return CurrentCursorInfo; } void SetHardwareCursor(CursorInfo cursorInfo) { #if SDL_VERSION_ATLEAST(2, 0, 0) CurrentCursorInfo = cursorInfo; CurrentCursorInfo.setNeedsReinitialization(false); switch (cursorInfo.type()) { case CursorType::Game: #if LOG_HWCURSOR Log("hwcursor: SetHardwareCursor Game"); #endif CurrentCursorInfo.SetEnabled(SetHardwareCursorFromSprite(cursorInfo.id())); break; case CursorType::UserInterface: #if LOG_HWCURSOR Log("hwcursor: SetHardwareCursor UserInterface"); #endif // ArtCursor is null while loading the game on the progress screen, // called via palette fade from ShowProgress. CurrentCursorInfo.SetEnabled( ArtCursor && IsCursorSizeAllowed(Size { (*ArtCursor)[0].width(), (*ArtCursor)[0].height() }) && SetHardwareCursorFromClxSprite((*ArtCursor)[0], HotpointPosition::TopLeft)); break; case CursorType::Unknown: #if LOG_HWCURSOR Log("hwcursor: SetHardwareCursor Unknown"); #endif CurrentCursorInfo.SetEnabled(false); break; } if (!CurrentCursorInfo.Enabled()) SetHardwareCursorVisible(false); #endif } } // namespace devilution