mirror of
https://github.com/diasurgical/DevilutionX.git
synced 2026-10-12 02:13:06 +00:00
This cleans up a bit of code and solves a few edge cases where an item could be lost, the game be unresponsive, or miss fire an event during lag cause of the cursor not reflecting the currently held item.
161 lines
4.6 KiB
C++
161 lines
4.6 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 "appfat.h"
|
|
#include "cursor.h"
|
|
#include "engine.h"
|
|
#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 {
|
|
TopLeft,
|
|
Center,
|
|
};
|
|
|
|
Size ScaledSize(Size size)
|
|
{
|
|
if (renderer != nullptr) {
|
|
float scaleX;
|
|
float scaleY;
|
|
SDL_RenderGetScale(renderer, &scaleX, &scaleY);
|
|
size.width = static_cast<int>(size.width * scaleX);
|
|
size.height = static_cast<int>(size.height * scaleY);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
bool IsCursorSizeAllowed(Size size)
|
|
{
|
|
if (*sgOptions.Graphics.hardwareCursorMaxSize <= 0)
|
|
return true;
|
|
size = ScaledSize(size);
|
|
return size.width <= *sgOptions.Graphics.hardwareCursorMaxSize
|
|
&& size.height <= *sgOptions.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 *sgOptions.Graphics.scaleQuality != ScalingQuality::NearestPixel;
|
|
}
|
|
|
|
bool SetHardwareCursor(SDL_Surface *surface, HotpointPosition hotpointPosition)
|
|
{
|
|
SDLCursorUniquePtr newCursor;
|
|
const Size size { surface->w, surface->h };
|
|
const Size scaledSize = ScaledSize(size);
|
|
|
|
if (size == scaledSize) {
|
|
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()) {
|
|
BilinearScale32(converted.get(), scaledSurface.get());
|
|
} else {
|
|
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)
|
|
return false;
|
|
SDL_SetCursor(newCursor.get());
|
|
CurrentCursor = std::move(newCursor);
|
|
return true;
|
|
}
|
|
|
|
bool SetHardwareCursorFromSprite(int pcurs)
|
|
{
|
|
const bool isItem = !MyPlayer->HoldItem.isEmpty();
|
|
if (isItem && !*sgOptions.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);
|
|
CelDrawCursor(out, { outlineWidth, size.height - outlineWidth }, pcurs);
|
|
|
|
const bool result = SetHardwareCursor(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;
|
|
switch (cursorInfo.type()) {
|
|
case CursorType::Game:
|
|
CurrentCursorInfo.SetEnabled(SetHardwareCursorFromSprite(cursorInfo.id()));
|
|
break;
|
|
case CursorType::UserInterface:
|
|
// ArtCursor is null while loading the game on the progress screen,
|
|
// called via palette fade from ShowProgress.
|
|
CurrentCursorInfo.SetEnabled(
|
|
ArtCursor.surface != nullptr && IsCursorSizeAllowed(Size { ArtCursor.surface->w, ArtCursor.surface->h })
|
|
&& SetHardwareCursor(ArtCursor.surface.get(), HotpointPosition::TopLeft));
|
|
break;
|
|
case CursorType::Unknown:
|
|
CurrentCursorInfo.SetEnabled(false);
|
|
break;
|
|
}
|
|
if (!CurrentCursorInfo.Enabled())
|
|
SetHardwareCursorVisible(false);
|
|
#endif
|
|
}
|
|
|
|
} // namespace devilution
|