#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.h" #include "utils/display.h" #include "utils/sdl_ptrs.h" namespace devilution { namespace { CursorInfo CurrentCursorInfo; #if SDL_VERSION_ATLEAST(2, 0, 0) SDLCursorUniquePtr CurrentCursor; enum class HotpointPosition { TopLeft, Center, }; 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"); } void SetHardwareCursor(SDL_Surface *surface, HotpointPosition hotpointPosition) { float scaleX; float scaleY; if (renderer != nullptr) { SDL_RenderGetScale(renderer, &scaleX, &scaleY); } SDLCursorUniquePtr newCursor; if (renderer == nullptr || (scaleX == 1.0F && scaleY == 1.0F)) { 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) }; const int scaledW = surface->w * scaleX; // NOLINT(bugprone-narrowing-conversions) const int scaledH = surface->h * scaleY; // NOLINT(bugprone-narrowing-conversions) SDLSurfaceUniquePtr scaledSurface { SDL_CreateRGBSurfaceWithFormat(0, scaledW, scaledH, 32, SDL_PIXELFORMAT_ARGB8888) }; SDL_BlitScaled(converted.get(), nullptr, scaledSurface.get(), nullptr); const Point hotpoint = GetHotpointPosition(*scaledSurface, hotpointPosition); newCursor = SDLCursorUniquePtr { SDL_CreateColorCursor(scaledSurface.get(), hotpoint.x, hotpoint.y) }; } SDL_SetCursor(newCursor.get()); CurrentCursor = std::move(newCursor); } void SetHardwareCursorFromSprite(int pcurs) { const bool isItem = IsItemSprite(pcurs); const int outlineWidth = isItem ? 1 : 0; int width; int height; std::tie(width, height) = GetInvItemSize(pcurs); width += 2 * outlineWidth; height += 2 * outlineWidth; auto out = CelOutputBuffer::Alloc(width, height); SDL_SetSurfacePalette(out.surface, palette); // 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); CelDrawCursor(out, { outlineWidth, height - outlineWidth }, pcurs); SetHardwareCursor(out.surface, isItem ? HotpointPosition::Center : HotpointPosition::TopLeft); out.Free(); } #endif } // namespace CursorInfo GetCurrentCursorInfo() { return CurrentCursorInfo; } void SetHardwareCursor(CursorInfo cursorInfo) { #if SDL_VERSION_ATLEAST(2, 0, 0) CurrentCursorInfo = cursorInfo; switch (cursorInfo.type()) { case CursorType::Game: SetHardwareCursorFromSprite(cursorInfo.id()); break; case CursorType::UserInterface: // ArtCursor is null while loading the game on the progress screen, // called via palette fade from ShowProgress. if (ArtCursor.surface != nullptr) SetHardwareCursor(ArtCursor.surface.get(), HotpointPosition::TopLeft); break; case CursorType::Unknown: break; } #endif } } // namespace devilution