devilutionX/Source/hwcursor.cpp
Gleb Mazovetskiy f9d20b44d6 🎉 Hardware cursor (SDL2-only)
Disabled by default because of these known issues:

1. When clicking on inventory item, it briefly appears a bit shifted (in the wrong coordinates).

   This issue can happen with software cursor as well, but is a lot more
   obvious with the hardware cursor.

2. Cursor is scaled with nearest-neighbour scaling, which may look a bit different from
   how the rest of the graphics are scaled.

See also previous attempt: https://github.com/diasurgical/devilutionX/pull/955 by @viciious

Co-authored-by: Victor Luchits <vluchits@gmail.com>
2021-06-19 01:03:40 +02:00

103 lines
2.9 KiB
C++

#include "hwcursor.hpp"
#include <cstdint>
#include <tuple>
#if SDL_VERSION_ATLEAST(2, 0, 0)
#include <SDL_mouse.h>
#include <SDL_render.h>
#include <SDL_surface.h>
#endif
#include "DiabloUI/diabloui.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;
void SetHardwareCursor(SDL_Surface *surface)
{
float scaleX;
float scaleY;
if (renderer != nullptr) {
SDL_RenderGetScale(renderer, &scaleX, &scaleY);
}
SDLCursorUniquePtr newCursor;
if (renderer == nullptr || (scaleX == 1.0F && scaleY == 1.0F)) {
newCursor = SDLCursorUniquePtr { SDL_CreateColorCursor(surface, 0, 0) };
} 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);
newCursor = SDLCursorUniquePtr { SDL_CreateColorCursor(scaledSurface.get(), 0, 0) };
}
SDL_SetCursor(newCursor.get());
CurrentCursor = std::move(newCursor);
}
void SetHardwareCursorFromSprite(int pcurs)
{
const int outlineWidth = IsItemSprite(pcurs) ? 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);
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());
break;
case CursorType::Unknown:
break;
}
#endif
}
} // namespace devilution