#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 bitmapSurface = bm_to_sdl_surface(bitmapNum); // For now set the hot coordinates to the upper left corner SDL_Cursor* cursorHandle = SDL_CreateColorCursor(bitmapSurface, 0, 0); SDL_FreeSurface(bitmapSurface); return cursorHandle; } void setRelativeMouseMode(bool grab) { if (Cmdline_nograb) { // Never grab the mouse if this is enabled SDL_SetRelativeMouseMode(SDL_FALSE); } else { SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE); } } void changeMouseStatus(bool show, bool grab) { if (show) { // If shown don't grab the mouse setRelativeMouseMode(false); SDL_ShowCursor(1); } else { if (grab) { setRelativeMouseMode(true); } else { setRelativeMouseMode(false); } SDL_ShowCursor(0); } } } namespace io { namespace mouse { Cursor::Cursor(Cursor&& other) noexcept { *this = std::move(other); } Cursor& Cursor::operator=(Cursor&& other) noexcept { std::swap(this->mAnimationFrames, other.mAnimationFrames); this->mBitmapHandle = other.mBitmapHandle; this->mBeginTimeStamp = other.mBeginTimeStamp; this->mLastFrame = other.mLastFrame; other.mBitmapHandle = -1; other.mBeginTimeStamp= -1; other.mLastFrame = static_cast(-1); return *this; } Cursor::~Cursor() { SCP_vector::iterator iter; for (iter = mAnimationFrames.begin(); iter != mAnimationFrames.end(); ++iter) { SDL_FreeCursor(*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 = 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(timestamp() - 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; } } } 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 != GR_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; } } }