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