CursorHandlerBase.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /*
  2. * CCursorHandler.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "CursorHandler.h"
  12. #include "SDL_Extensions.h"
  13. #include "CGuiHandler.h"
  14. #include "CAnimation.h"
  15. #include "../../lib/CConfigHandler.h"
  16. #include <SDL_render.h>
  17. #include <SDL_events.h>
  18. #ifdef VCMI_APPLE
  19. #include <dispatch/dispatch.h>
  20. #endif
  21. std::unique_ptr<ICursor> CursorHandler::createCursor()
  22. {
  23. if (settings["video"]["cursor"].String() == "auto")
  24. {
  25. #if defined(VCMI_ANDROID) || defined(VCMI_IOS)
  26. return std::make_unique<CursorSoftware>();
  27. #else
  28. return std::make_unique<CursorHardware>();
  29. #endif
  30. }
  31. if (settings["video"]["cursor"].String() == "hardware")
  32. return std::make_unique<CursorHardware>();
  33. assert(settings["video"]["cursor"].String() == "software");
  34. return std::make_unique<CursorSoftware>();
  35. }
  36. CursorHandler::CursorHandler()
  37. : cursor(createCursor())
  38. , frameTime(0.f)
  39. , showing(false)
  40. , pos(0,0)
  41. {
  42. type = Cursor::Type::DEFAULT;
  43. dndObject = nullptr;
  44. cursors =
  45. {
  46. std::make_unique<CAnimation>("CRADVNTR"),
  47. std::make_unique<CAnimation>("CRCOMBAT"),
  48. std::make_unique<CAnimation>("CRDEFLT"),
  49. std::make_unique<CAnimation>("CRSPELL")
  50. };
  51. for (auto & cursor : cursors)
  52. cursor->preload();
  53. set(Cursor::Map::POINTER);
  54. }
  55. Point CursorHandler::position() const
  56. {
  57. return pos;
  58. }
  59. void CursorHandler::changeGraphic(Cursor::Type type, size_t index)
  60. {
  61. assert(dndObject == nullptr);
  62. if (type == this->type && index == this->frame)
  63. return;
  64. this->type = type;
  65. this->frame = index;
  66. cursor->setImage(getCurrentImage(), getPivotOffset());
  67. }
  68. void CursorHandler::set(Cursor::Default index)
  69. {
  70. changeGraphic(Cursor::Type::DEFAULT, static_cast<size_t>(index));
  71. }
  72. void CursorHandler::set(Cursor::Map index)
  73. {
  74. changeGraphic(Cursor::Type::ADVENTURE, static_cast<size_t>(index));
  75. }
  76. void CursorHandler::set(Cursor::Combat index)
  77. {
  78. changeGraphic(Cursor::Type::COMBAT, static_cast<size_t>(index));
  79. }
  80. void CursorHandler::set(Cursor::Spellcast index)
  81. {
  82. //Note: this is animated cursor, ignore specified frame and only change type
  83. changeGraphic(Cursor::Type::SPELLBOOK, frame);
  84. }
  85. void CursorHandler::dragAndDropCursor(std::shared_ptr<IImage> image)
  86. {
  87. dndObject = image;
  88. cursor->setImage(getCurrentImage(), getPivotOffset());
  89. }
  90. void CursorHandler::dragAndDropCursor (std::string path, size_t index)
  91. {
  92. CAnimation anim(path);
  93. anim.load(index);
  94. dragAndDropCursor(anim.getImage(index));
  95. }
  96. void CursorHandler::cursorMove(const int & x, const int & y)
  97. {
  98. pos.x = x;
  99. pos.y = y;
  100. cursor->setCursorPosition(pos);
  101. }
  102. Point CursorHandler::getPivotOffsetDefault(size_t index)
  103. {
  104. return {0, 0};
  105. }
  106. Point CursorHandler::getPivotOffsetMap(size_t index)
  107. {
  108. static const std::array<Point, 43> offsets = {{
  109. { 0, 0}, // POINTER = 0,
  110. { 0, 0}, // HOURGLASS = 1,
  111. { 12, 10}, // HERO = 2,
  112. { 12, 12}, // TOWN = 3,
  113. { 15, 13}, // T1_MOVE = 4,
  114. { 13, 13}, // T1_ATTACK = 5,
  115. { 16, 32}, // T1_SAIL = 6,
  116. { 13, 20}, // T1_DISEMBARK = 7,
  117. { 8, 9}, // T1_EXCHANGE = 8,
  118. { 14, 16}, // T1_VISIT = 9,
  119. { 15, 13}, // T2_MOVE = 10,
  120. { 13, 13}, // T2_ATTACK = 11,
  121. { 16, 32}, // T2_SAIL = 12,
  122. { 13, 20}, // T2_DISEMBARK = 13,
  123. { 8, 9}, // T2_EXCHANGE = 14,
  124. { 14, 16}, // T2_VISIT = 15,
  125. { 15, 13}, // T3_MOVE = 16,
  126. { 13, 13}, // T3_ATTACK = 17,
  127. { 16, 32}, // T3_SAIL = 18,
  128. { 13, 20}, // T3_DISEMBARK = 19,
  129. { 8, 9}, // T3_EXCHANGE = 20,
  130. { 14, 16}, // T3_VISIT = 21,
  131. { 15, 13}, // T4_MOVE = 22,
  132. { 13, 13}, // T4_ATTACK = 23,
  133. { 16, 32}, // T4_SAIL = 24,
  134. { 13, 20}, // T4_DISEMBARK = 25,
  135. { 8, 9}, // T4_EXCHANGE = 26,
  136. { 14, 16}, // T4_VISIT = 27,
  137. { 16, 32}, // T1_SAIL_VISIT = 28,
  138. { 16, 32}, // T2_SAIL_VISIT = 29,
  139. { 16, 32}, // T3_SAIL_VISIT = 30,
  140. { 16, 32}, // T4_SAIL_VISIT = 31,
  141. { 6, 1}, // SCROLL_NORTH = 32,
  142. { 16, 2}, // SCROLL_NORTHEAST = 33,
  143. { 21, 6}, // SCROLL_EAST = 34,
  144. { 16, 16}, // SCROLL_SOUTHEAST = 35,
  145. { 6, 21}, // SCROLL_SOUTH = 36,
  146. { 1, 16}, // SCROLL_SOUTHWEST = 37,
  147. { 1, 5}, // SCROLL_WEST = 38,
  148. { 2, 1}, // SCROLL_NORTHWEST = 39,
  149. { 0, 0}, // POINTER_COPY = 40,
  150. { 14, 16}, // TELEPORT = 41,
  151. { 20, 20}, // SCUTTLE_BOAT = 42
  152. }};
  153. assert(offsets.size() == size_t(Cursor::Map::COUNT)); //Invalid number of pivot offsets for cursor
  154. assert(index < offsets.size());
  155. return offsets[index];
  156. }
  157. Point CursorHandler::getPivotOffsetCombat(size_t index)
  158. {
  159. static const std::array<Point, 20> offsets = {{
  160. { 12, 12 }, // BLOCKED = 0,
  161. { 10, 14 }, // MOVE = 1,
  162. { 14, 14 }, // FLY = 2,
  163. { 12, 12 }, // SHOOT = 3,
  164. { 12, 12 }, // HERO = 4,
  165. { 8, 12 }, // QUERY = 5,
  166. { 0, 0 }, // POINTER = 6,
  167. { 21, 0 }, // HIT_NORTHEAST = 7,
  168. { 31, 5 }, // HIT_EAST = 8,
  169. { 21, 21 }, // HIT_SOUTHEAST = 9,
  170. { 0, 21 }, // HIT_SOUTHWEST = 10,
  171. { 0, 5 }, // HIT_WEST = 11,
  172. { 0, 0 }, // HIT_NORTHWEST = 12,
  173. { 6, 0 }, // HIT_NORTH = 13,
  174. { 6, 31 }, // HIT_SOUTH = 14,
  175. { 14, 0 }, // SHOOT_PENALTY = 15,
  176. { 12, 12 }, // SHOOT_CATAPULT = 16,
  177. { 12, 12 }, // HEAL = 17,
  178. { 12, 12 }, // SACRIFICE = 18,
  179. { 14, 20 }, // TELEPORT = 19
  180. }};
  181. assert(offsets.size() == size_t(Cursor::Combat::COUNT)); //Invalid number of pivot offsets for cursor
  182. assert(index < offsets.size());
  183. return offsets[index];
  184. }
  185. Point CursorHandler::getPivotOffsetSpellcast()
  186. {
  187. return { 18, 28};
  188. }
  189. Point CursorHandler::getPivotOffset()
  190. {
  191. if (dndObject)
  192. return dndObject->dimensions() / 2;
  193. switch (type) {
  194. case Cursor::Type::ADVENTURE: return getPivotOffsetMap(frame);
  195. case Cursor::Type::COMBAT: return getPivotOffsetCombat(frame);
  196. case Cursor::Type::DEFAULT: return getPivotOffsetDefault(frame);
  197. case Cursor::Type::SPELLBOOK: return getPivotOffsetSpellcast();
  198. };
  199. assert(0);
  200. return {0, 0};
  201. }
  202. std::shared_ptr<IImage> CursorHandler::getCurrentImage()
  203. {
  204. if (dndObject)
  205. return dndObject;
  206. return cursors[static_cast<size_t>(type)]->getImage(frame);
  207. }
  208. void CursorHandler::centerCursor()
  209. {
  210. Point screenSize {screen->w, screen->h};
  211. pos = screenSize / 2 - getPivotOffset();
  212. SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
  213. CSDL_Ext::warpMouse(pos.x, pos.y);
  214. SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
  215. cursor->setCursorPosition(pos);
  216. }
  217. void CursorHandler::updateSpellcastCursor()
  218. {
  219. static const float frameDisplayDuration = 0.1f; // H3 uses 100 ms per frame
  220. frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
  221. size_t newFrame = frame;
  222. while (frameTime >= frameDisplayDuration)
  223. {
  224. frameTime -= frameDisplayDuration;
  225. newFrame++;
  226. }
  227. auto & animation = cursors.at(static_cast<size_t>(type));
  228. while (newFrame >= animation->size())
  229. newFrame -= animation->size();
  230. changeGraphic(Cursor::Type::SPELLBOOK, newFrame);
  231. }
  232. void CursorHandler::render()
  233. {
  234. if(!showing)
  235. return;
  236. if (type == Cursor::Type::SPELLBOOK)
  237. updateSpellcastCursor();
  238. cursor->render();
  239. }
  240. void CursorHandler::hide()
  241. {
  242. if (!showing)
  243. return;
  244. showing = false;
  245. cursor->setVisible(false);
  246. }
  247. void CursorHandler::show()
  248. {
  249. if (showing)
  250. return;
  251. showing = true;
  252. cursor->setVisible(true);
  253. }
  254. void CursorSoftware::render()
  255. {
  256. //texture must be updated in the main (renderer) thread, but changes to cursor type may come from other threads
  257. if (needUpdate)
  258. updateTexture();
  259. Point renderPos = pos - pivot;
  260. SDL_Rect destRect;
  261. destRect.x = renderPos.x;
  262. destRect.y = renderPos.y;
  263. destRect.w = 40;
  264. destRect.h = 40;
  265. SDL_RenderCopy(mainRenderer, cursorTexture, nullptr, &destRect);
  266. }
  267. void CursorSoftware::createTexture(const Point & dimensions)
  268. {
  269. if(cursorTexture)
  270. SDL_DestroyTexture(cursorTexture);
  271. if (cursorSurface)
  272. SDL_FreeSurface(cursorSurface);
  273. cursorSurface = CSDL_Ext::newSurface(dimensions.x, dimensions.y);
  274. cursorTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y);
  275. SDL_SetSurfaceBlendMode(cursorSurface, SDL_BLENDMODE_NONE);
  276. SDL_SetTextureBlendMode(cursorTexture, SDL_BLENDMODE_BLEND);
  277. }
  278. void CursorSoftware::updateTexture()
  279. {
  280. Point dimensions(-1, -1);
  281. if (!cursorSurface || Point(cursorSurface->w, cursorSurface->h) != cursorImage->dimensions())
  282. createTexture(cursorImage->dimensions());
  283. CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENCY);
  284. cursorImage->draw(cursorSurface);
  285. SDL_UpdateTexture(cursorTexture, NULL, cursorSurface->pixels, cursorSurface->pitch);
  286. needUpdate = false;
  287. }
  288. void CursorSoftware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
  289. {
  290. assert(image != nullptr);
  291. cursorImage = image;
  292. pivot = pivotOffset;
  293. needUpdate = true;
  294. }
  295. void CursorSoftware::setCursorPosition( const Point & newPos )
  296. {
  297. pos = newPos;
  298. }
  299. void CursorSoftware::setVisible(bool on)
  300. {
  301. visible = on;
  302. }
  303. CursorSoftware::CursorSoftware():
  304. cursorTexture(nullptr),
  305. cursorSurface(nullptr),
  306. needUpdate(false),
  307. visible(false),
  308. pivot(0,0)
  309. {
  310. SDL_ShowCursor(SDL_DISABLE);
  311. }
  312. CursorSoftware::~CursorSoftware()
  313. {
  314. if(cursorTexture)
  315. SDL_DestroyTexture(cursorTexture);
  316. if (cursorSurface)
  317. SDL_FreeSurface(cursorSurface);
  318. }
  319. CursorHardware::CursorHardware():
  320. cursor(nullptr)
  321. {
  322. SDL_ShowCursor(SDL_DISABLE);
  323. }
  324. CursorHardware::~CursorHardware()
  325. {
  326. if(cursor)
  327. SDL_FreeCursor(cursor);
  328. }
  329. void CursorHardware::setVisible(bool on)
  330. {
  331. #ifdef VCMI_APPLE
  332. dispatch_async(dispatch_get_main_queue(), ^{
  333. #endif
  334. if (on)
  335. SDL_ShowCursor(SDL_ENABLE);
  336. else
  337. SDL_ShowCursor(SDL_DISABLE);
  338. #ifdef VCMI_APPLE
  339. });
  340. #endif
  341. }
  342. void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
  343. {
  344. auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y);
  345. CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENCY);
  346. image->draw(cursorSurface);
  347. auto oldCursor = cursor;
  348. cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y);
  349. if (!cursor)
  350. logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError());
  351. SDL_FreeSurface(cursorSurface);
  352. #ifdef VCMI_APPLE
  353. dispatch_async(dispatch_get_main_queue(), ^{
  354. #endif
  355. SDL_SetCursor(cursor);
  356. if (oldCursor)
  357. SDL_FreeCursor(oldCursor);
  358. #ifdef VCMI_APPLE
  359. });
  360. #endif
  361. }
  362. void CursorHardware::setCursorPosition( const Point & newPos )
  363. {
  364. //no-op
  365. }
  366. void CursorHardware::render()
  367. {
  368. //no-op
  369. }