#include #include #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 #include #include 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(-1)); return *this; } Cursor::~Cursor() { SCP_vector::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(-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(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(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 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; } } }