Files
Taylor Richards 039880491a port to SDL3 (#7220)
Basic port from SDL2 to SDL3. Also updates imgui to 1.92.5.
2026-07-23 15:52:27 +02:00

295 lines
6.2 KiB
C++

#include <cmath>
#include <algorithm>
#include "cmdline/cmdline.h"
#include "bmpman/bmpman.h"
#include "io/cursor.h"
#include "io/timer.h"
#include "gamesequence/gamesequence.h"
#include "popup/popup.h"
#include "popup/popupdead.h"
#include <utility>
#include <globalincs/systemvars.h>
#include <graphics/2d.h>
namespace
{
SDL_Cursor* bitmapToCursor(int bitmapNum)
{
auto surface = bm_to_sdl_surface(bitmapNum);
if ( !surface ) {
return nullptr;
}
// create alternate 2x version for high-dpi
auto surfaceScaled = SDL_ScaleSurface(surface,
surface->w * 2,
surface->h * 2,
SDL_SCALEMODE_LINEAR);
if (surfaceScaled) {
SDL_AddSurfaceAlternateImage(surface, surfaceScaled);
SDL_DestroySurface(surfaceScaled);
surfaceScaled = nullptr;
}
// For now set the hot coordinates to the upper left corner
SDL_Cursor* cursorHandle = SDL_CreateColorCursor(surface, 0, 0);
SDL_DestroySurface(surface);
return cursorHandle;
}
void setRelativeMouseMode(bool grab) {
if (Cmdline_nograb) {
// Never grab the mouse if this is enabled
SDL_SetWindowRelativeMouseMode(os::getSDLMainWindow(), false);
} else {
SDL_SetWindowRelativeMouseMode(os::getSDLMainWindow(), grab);
}
}
void changeMouseStatus(bool show, bool grab)
{
if (show)
{
// If shown don't grab the mouse
setRelativeMouseMode(false);
SDL_ShowCursor();
}
else
{
if (grab)
{
setRelativeMouseMode(true);
}
else
{
setRelativeMouseMode(false);
}
SDL_HideCursor();
}
}
}
namespace io
{
namespace mouse
{
Cursor::Cursor(Cursor&& other) noexcept
{
*this = std::move(other);
}
Cursor& Cursor::operator=(Cursor&& other) noexcept
{
if (this == &other)
return *this;
std::swap(this->mAnimationFrames, other.mAnimationFrames);
this->mBitmapHandle = std::exchange(other.mBitmapHandle, -1);
this->mBeginTimeStamp = std::exchange(other.mBeginTimeStamp, UI_TIMESTAMP::invalid());
this->mLastFrame = std::exchange(other.mLastFrame, static_cast<size_t>(-1));
return *this;
}
Cursor::~Cursor()
{
SCP_vector<SDL_Cursor*>::iterator iter;
for (iter = mAnimationFrames.begin(); iter != mAnimationFrames.end(); ++iter)
{
SDL_DestroyCursor(*iter);
}
mAnimationFrames.clear();
// Free cursor
bm_release(mBitmapHandle);
mBitmapHandle = -1;
}
void Cursor::addFrame(SDL_Cursor* frame)
{
mAnimationFrames.push_back(frame);
}
void Cursor::enable()
{
if (mAnimationFrames.size() > 1)
{
// Animated, set the begin and do everything else in setCurrentFrame()
mBeginTimeStamp = ui_timestamp();
mLastFrame = static_cast<size_t>(-1);
}
else
{
// Not animated, just set the first frame
SDL_SetCursor(mAnimationFrames.front());
}
}
void Cursor::setCurrentFrame()
{
if (mAnimationFrames.size() > 1)
{
// We are animated, compute the current frame
float diffSeconds = i2fl(ui_timestamp_since(mBeginTimeStamp)) / TIMESTAMP_FREQUENCY;
// Use the bmpman function for this. That also ensures that APNG cursors work correctly
auto frameIndex = static_cast<size_t>(bm_get_anim_frame(mBitmapHandle, diffSeconds, 0.0f, true));
Assert(frameIndex < mAnimationFrames.size());
if (mLastFrame != frameIndex)
{
SDL_SetCursor(mAnimationFrames[frameIndex]);
mLastFrame = frameIndex;
}
}
}
int Cursor::getBitmapHandle()
{
return mBitmapHandle;
}
CursorManager* CursorManager::mSingleton = nullptr;
CursorManager::CursorManager() : mCurrentCursor(nullptr)
{
mStatusStack.push_back(std::make_pair(true, false));
}
CursorManager::~CursorManager()
{
}
Cursor* CursorManager::loadCursor(const char* fileName, bool animated)
{
int handle;
if (animated)
{
handle = bm_load_animation(fileName, nullptr, nullptr);
}
else
{
handle = bm_load(fileName);
}
if (handle < 0)
{
mprintf(("Failed to load cursor bitmap %s!\n", fileName));
return nullptr;
}
Cursor* cursor = this->loadFromBitmap(handle);
return cursor;
}
Cursor* CursorManager::loadFromBitmap(int bitmapHandle)
{
Assertion(gr_screen.mode != GraphicsAPI::Stub, "Cursors can not be used with the stub renderer!");
Assertion(bm_is_valid(bitmapHandle), "%d is no valid bitmap handle!", bitmapHandle);
int nframes;
int fps;
bm_get_info(bitmapHandle, nullptr, nullptr, nullptr, &nframes, &fps);
std::unique_ptr<Cursor> cursor(new Cursor(bitmapHandle));
for (int i = 0; i < nframes; ++i)
{
auto sdlCursor = bitmapToCursor(bitmapHandle + i);
if (sdlCursor != nullptr) {
cursor->addFrame(sdlCursor);
} else {
// Failed to convert bitmap
return nullptr;
}
}
mLoadedCursors.push_back(std::move(cursor));
return mLoadedCursors.back().get();
}
void CursorManager::setCurrentCursor(Cursor* cursor)
{
Assertion(cursor, "Invalid cursor pointer passed!");
mCurrentCursor = cursor;
mCurrentCursor->enable();
}
void CursorManager::showCursor(bool show, bool grab)
{
auto current = mStatusStack.back();
if (show == current.first && grab == current.second)
{
// Don't bother calling anithing if it's not going to change anything
return;
}
changeMouseStatus(show, grab);
mStatusStack.back() = std::make_pair(show, grab);
}
void CursorManager::pushStatus()
{
// Copy the last status into the top
mStatusStack.push_back(mStatusStack.back());
}
std::pair<bool, bool> CursorManager::popStatus()
{
Assertion(mStatusStack.size() > 1, "Can't pop the last status!");
auto current = mStatusStack.back();
mStatusStack.pop_back();
auto newState = mStatusStack.back();
changeMouseStatus(newState.first, newState.second);
return current;
}
void CursorManager::init()
{
mSingleton = new CursorManager();
// Hide the cursor initially
mSingleton->showCursor(false);
}
void CursorManager::doFrame()
{
CursorManager* manager = get();
if (manager->mCurrentCursor != nullptr && manager->isCursorShown())
{
manager->mCurrentCursor->setCurrentFrame();
}
}
void CursorManager::shutdown()
{
delete mSingleton;
}
}
}