1 2 #include <cmath> 3 #include <algorithm> 4 5 #include "cmdline/cmdline.h" 6 #include "bmpman/bmpman.h" 7 #include "io/cursor.h" 8 #include "io/timer.h" 9 #include "gamesequence/gamesequence.h" 10 #include "popup/popup.h" 11 #include "popup/popupdead.h" 12 13 #include <utility> 14 #include <globalincs/systemvars.h> 15 #include <graphics/2d.h> 16 17 namespace 18 { bitmapToCursor(int bitmapNum)19 SDL_Cursor* bitmapToCursor(int bitmapNum) 20 { 21 auto bitmapSurface = bm_to_sdl_surface(bitmapNum); 22 23 // For now set the hot coordinates to the upper left corner 24 SDL_Cursor* cursorHandle = SDL_CreateColorCursor(bitmapSurface, 0, 0); 25 26 SDL_FreeSurface(bitmapSurface); 27 28 return cursorHandle; 29 } 30 setRelativeMouseMode(bool grab)31 void setRelativeMouseMode(bool grab) { 32 if (Cmdline_nograb) { 33 // Never grab the mouse if this is enabled 34 SDL_SetRelativeMouseMode(SDL_FALSE); 35 } else { 36 SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE); 37 } 38 } 39 changeMouseStatus(bool show,bool grab)40 void changeMouseStatus(bool show, bool grab) 41 { 42 if (show) 43 { 44 // If shown don't grab the mouse 45 setRelativeMouseMode(false); 46 SDL_ShowCursor(1); 47 } 48 else 49 { 50 if (grab) 51 { 52 setRelativeMouseMode(true); 53 } 54 else 55 { 56 setRelativeMouseMode(false); 57 } 58 59 SDL_ShowCursor(0); 60 } 61 } 62 } 63 64 namespace io 65 { 66 namespace mouse 67 { 68 Cursor(Cursor && other)69 Cursor::Cursor(Cursor&& other) noexcept 70 { 71 *this = std::move(other); 72 } 73 operator =(Cursor && other)74 Cursor& Cursor::operator=(Cursor&& other) noexcept 75 { 76 std::swap(this->mAnimationFrames, other.mAnimationFrames); 77 78 this->mBitmapHandle = other.mBitmapHandle; 79 this->mBeginTimeStamp = other.mBeginTimeStamp; 80 this->mLastFrame = other.mLastFrame; 81 82 other.mBitmapHandle = -1; 83 other.mBeginTimeStamp= -1; 84 other.mLastFrame = static_cast<size_t>(-1); 85 86 return *this; 87 } 88 ~Cursor()89 Cursor::~Cursor() 90 { 91 SCP_vector<SDL_Cursor*>::iterator iter; 92 93 for (iter = mAnimationFrames.begin(); iter != mAnimationFrames.end(); ++iter) 94 { 95 SDL_FreeCursor(*iter); 96 } 97 98 mAnimationFrames.clear(); 99 100 // Free cursor 101 bm_release(mBitmapHandle); 102 mBitmapHandle = -1; 103 } 104 addFrame(SDL_Cursor * frame)105 void Cursor::addFrame(SDL_Cursor* frame) 106 { 107 mAnimationFrames.push_back(frame); 108 } 109 enable()110 void Cursor::enable() 111 { 112 if (mAnimationFrames.size() > 1) 113 { 114 // Animated, set the begin and do everything else in setCurrentFrame() 115 mBeginTimeStamp = timestamp(); 116 mLastFrame = static_cast<size_t>(-1); 117 } 118 else 119 { 120 // Not animated, just set the first frame 121 SDL_SetCursor(mAnimationFrames.front()); 122 } 123 } 124 setCurrentFrame()125 void Cursor::setCurrentFrame() 126 { 127 if (mAnimationFrames.size() > 1) 128 { 129 // We are animated, compute the current frame 130 float diffSeconds = i2fl(timestamp() - mBeginTimeStamp) / TIMESTAMP_FREQUENCY; 131 132 // Use the bmpman function for this. That also ensures that APNG cursors work correctly 133 auto frameIndex = static_cast<size_t>(bm_get_anim_frame(mBitmapHandle, diffSeconds, 0.0f, true)); 134 135 Assert(frameIndex < mAnimationFrames.size()); 136 137 if (mLastFrame != frameIndex) 138 { 139 SDL_SetCursor(mAnimationFrames[frameIndex]); 140 mLastFrame = frameIndex; 141 } 142 } 143 } 144 145 CursorManager* CursorManager::mSingleton = nullptr; 146 CursorManager()147 CursorManager::CursorManager() : mCurrentCursor(nullptr) 148 { 149 mStatusStack.push_back(std::make_pair(true, false)); 150 } 151 ~CursorManager()152 CursorManager::~CursorManager() 153 { 154 } 155 loadCursor(const char * fileName,bool animated)156 Cursor* CursorManager::loadCursor(const char* fileName, bool animated) 157 { 158 int handle; 159 160 if (animated) 161 { 162 handle = bm_load_animation(fileName, nullptr, nullptr); 163 } 164 else 165 { 166 handle = bm_load(fileName); 167 } 168 169 if (handle < 0) 170 { 171 mprintf(("Failed to load cursor bitmap %s!\n", fileName)); 172 return nullptr; 173 } 174 175 Cursor* cursor = this->loadFromBitmap(handle); 176 177 return cursor; 178 } 179 loadFromBitmap(int bitmapHandle)180 Cursor* CursorManager::loadFromBitmap(int bitmapHandle) 181 { 182 Assertion(gr_screen.mode != GR_STUB, "Cursors can not be used with the stub renderer!"); 183 184 Assertion(bm_is_valid(bitmapHandle), "%d is no valid bitmap handle!", bitmapHandle); 185 186 int nframes; 187 int fps; 188 189 bm_get_info(bitmapHandle, nullptr, nullptr, nullptr, &nframes, &fps); 190 191 std::unique_ptr<Cursor> cursor(new Cursor(bitmapHandle)); 192 193 for (int i = 0; i < nframes; ++i) 194 { 195 auto sdlCursor = bitmapToCursor(bitmapHandle + i); 196 if (sdlCursor != nullptr) { 197 cursor->addFrame(sdlCursor); 198 } else { 199 // Failed to convert bitmap 200 return nullptr; 201 } 202 } 203 204 mLoadedCursors.push_back(std::move(cursor)); 205 206 return mLoadedCursors.back().get(); 207 } 208 setCurrentCursor(Cursor * cursor)209 void CursorManager::setCurrentCursor(Cursor* cursor) 210 { 211 Assertion(cursor, "Invalid cursor pointer passed!"); 212 213 mCurrentCursor = cursor; 214 mCurrentCursor->enable(); 215 } 216 showCursor(bool show,bool grab)217 void CursorManager::showCursor(bool show, bool grab) 218 { 219 auto current = mStatusStack.back(); 220 if (show == current.first && grab == current.second) 221 { 222 // Don't bother calling anithing if it's not going to change anything 223 return; 224 } 225 226 changeMouseStatus(show, grab); 227 228 mStatusStack.back() = std::make_pair(show, grab); 229 } 230 pushStatus()231 void CursorManager::pushStatus() 232 { 233 // Copy the last status into the top 234 mStatusStack.push_back(mStatusStack.back()); 235 } 236 popStatus()237 std::pair<bool, bool> CursorManager::popStatus() 238 { 239 Assertion(mStatusStack.size() > 1, "Can't pop the last status!"); 240 241 auto current = mStatusStack.back(); 242 243 mStatusStack.pop_back(); 244 245 auto newState = mStatusStack.back(); 246 changeMouseStatus(newState.first, newState.second); 247 248 return current; 249 } 250 init()251 void CursorManager::init() 252 { 253 mSingleton = new CursorManager(); 254 255 // Hide the cursor initially 256 mSingleton->showCursor(false); 257 } 258 doFrame()259 void CursorManager::doFrame() 260 { 261 CursorManager* manager = get(); 262 263 if (manager->mCurrentCursor != nullptr && manager->isCursorShown()) 264 { 265 manager->mCurrentCursor->setCurrentFrame(); 266 } 267 } 268 shutdown()269 void CursorManager::shutdown() 270 { 271 delete mSingleton; 272 } 273 } 274 } 275