Open Source Tomb Raider Engine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Menu.cpp 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /*!
  2. * \file src/Menu.cpp
  3. * \brief Menu 'overlay'
  4. *
  5. * \author xythobuz
  6. */
  7. #include <cctype>
  8. #include <cstring>
  9. #include "global.h"
  10. #include "Console.h"
  11. #include "OpenRaider.h"
  12. #include "utils/strings.h"
  13. #include "TombRaider.h"
  14. #include "Window.h"
  15. #include "Menu.h"
  16. #if defined(HAVE_DIRENT_H) && defined(HAVE_OPENDIR) && defined(HAVE_READDIR_R) && defined(HAVE_CLOSEDIR) && defined(HAVE_DT_DIR)
  17. #include <dirent.h>
  18. #define USE_DIRENT
  19. #elif defined (_WIN32)
  20. #include <windows.h>
  21. #define USE_FINDFILE
  22. #else
  23. #error No support for recursive folder traversal
  24. #endif
  25. Menu::Menu() {
  26. mVisible = false;
  27. mCursor = 0;
  28. mMin = 0;
  29. mainText.text = bufferString("%s", VERSION);
  30. mainText.color[0] = BLUE[0];
  31. mainText.color[1] = BLUE[1];
  32. mainText.color[2] = BLUE[2];
  33. mainText.color[3] = BLUE[3];
  34. mainText.scale = 1.2f;
  35. mainText.y = 10;
  36. mainText.w = 0;
  37. mainText.h = 0;
  38. mMapListFilled = false;
  39. mFirstPass = false;
  40. }
  41. Menu::~Menu() {
  42. delete [] mainText.text;
  43. mainText.text = NULL;
  44. while (mMapList.size() > 0) {
  45. delete [] mMapList.back();
  46. mMapList.pop_back();
  47. }
  48. }
  49. void Menu::setVisible(bool visible) {
  50. mVisible = visible;
  51. }
  52. bool Menu::isVisible() {
  53. return mVisible;
  54. }
  55. void Menu::loadPakFolderRecursive(const char *dir) {
  56. assert(dir != NULL);
  57. assert(dir[0] != '\0');
  58. #ifdef USE_DIRENT
  59. struct dirent entry;
  60. struct dirent *ep = NULL;
  61. DIR *pakDir;
  62. pakDir = opendir(dir);
  63. if (pakDir != NULL) {
  64. readdir_r(pakDir, &entry, &ep);
  65. while (ep != NULL) {
  66. if (ep->d_type == DT_DIR) {
  67. if ((strcmp(".", ep->d_name) != 0)
  68. && (strcmp("..", ep->d_name) != 0)) {
  69. char *tmp = bufferString("%s%s", dir, ep->d_name);
  70. char *next = fullPath(tmp, '/');
  71. loadPakFolderRecursive(next);
  72. delete next;
  73. delete tmp;
  74. }
  75. } else {
  76. char *fullPathMap = bufferString("%s%s", dir, ep->d_name);
  77. char *lowerPath = bufferString("%s", fullPathMap);
  78. for (char *p = lowerPath; *p; ++p) *p = (char)tolower(*p);
  79. // Check for valid extension
  80. if (stringEndsWith(lowerPath, ".phd")
  81. || stringEndsWith(lowerPath, ".tr2")
  82. || stringEndsWith(lowerPath, ".tr4")
  83. || stringEndsWith(lowerPath, ".trc")) {
  84. int error = TombRaider::checkMime(fullPathMap);
  85. if (error == 0) {
  86. // Just load relative filename
  87. mMapList.push_back(bufferString("%s", (fullPathMap + strlen(getOpenRaider().mPakDir) + 1)));
  88. } else {
  89. getConsole().print("Error: pak file '%s' %s",
  90. fullPathMap, (error == -1) ? "not found" : "invalid");
  91. }
  92. }
  93. delete [] lowerPath;
  94. delete [] fullPathMap;
  95. }
  96. readdir_r(pakDir, &entry, &ep);
  97. }
  98. closedir(pakDir);
  99. } else {
  100. getConsole().print("Could not open PAK dir %s!", dir);
  101. }
  102. #elif defined(USE_FINDFILE)
  103. std::vector<char *> list;
  104. list.push_back(bufferString("%s", dir));
  105. do {
  106. WIN32_FIND_DATA fd;
  107. char *tmp = bufferString("%s\\*", list.at(0));
  108. HANDLE hFind = FindFirstFile(tmp, &fd);
  109. do {
  110. if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  111. list.push_back(bufferString("%s\\%s", list.at(0), fd.cFileName));
  112. } else {
  113. char *fullPathMap = bufferString("%s\\%s", list.at(0), fd.cFileName);
  114. char *lowerPath = bufferString("%s", fullPathMap);
  115. for (char *p = lowerPath; *p; ++p) *p = (char)tolower(*p);
  116. // Check for valid extension
  117. if (stringEndsWith(lowerPath, ".phd")
  118. || stringEndsWith(lowerPath, ".tr2")
  119. || stringEndsWith(lowerPath, ".tr4")
  120. || stringEndsWith(lowerPath, ".trc")) {
  121. int error = TombRaider::checkMime(fullPathMap);
  122. if (error == 0) {
  123. // Just load relative filename
  124. mMapList.push_back(bufferString("%s", (fullPathMap + strlen(getOpenRaider().mPakDir) + 1)));
  125. } else {
  126. getConsole().print("Error: pak file '%s' %s",
  127. fullPathMap, (error == -1) ? "not found" : "invalid");
  128. }
  129. }
  130. delete [] lowerPath;
  131. delete [] fullPathMap;
  132. }
  133. } while (FindNextFile(hFind, &fd) != 0);
  134. FindClose(hFind);
  135. delete [] tmp;
  136. delete [] list.at(0);
  137. list.erase(list.begin());
  138. } while (list.size() > 0);
  139. #endif
  140. }
  141. void Menu::fillMapList() {
  142. char *tmp = fullPath(getOpenRaider().mPakDir, '/');
  143. loadPakFolderRecursive(tmp);
  144. delete [] tmp;
  145. mMapListFilled = true;
  146. }
  147. void Menu::displayMapList() {
  148. // Estimate displayable number of items
  149. int items = (getWindow().getHeight() - 110) / 25;
  150. // Select which part of the list to show
  151. long min, max;
  152. if (((long)mCursor - (items / 2)) > 0)
  153. min = mCursor - (items / 2);
  154. else
  155. min = 0;
  156. if ((mCursor + (items / 2)) < mMapList.size())
  157. max = mCursor + (items / 2);
  158. else
  159. max = mMapList.size();
  160. while ((max - min) < items) {
  161. if (min > 0)
  162. min--;
  163. else if (max < ((int)mMapList.size()))
  164. max++;
  165. else
  166. break;
  167. }
  168. mMin = min;
  169. for (int i = 0; i < (max - min); i++) {
  170. char *map = mMapList[i + min];
  171. if ((i + min) == (int)mCursor)
  172. getFont().drawText(25, 100 + (25 * i), 0.75f, RED, "%s", map);
  173. else
  174. getFont().drawText(25, 100 + (25 * i), 0.75f, BLUE, "%s", map);
  175. }
  176. }
  177. void Menu::display() {
  178. if (mVisible) {
  179. // Draw half-transparent *overlay*
  180. glColor4f(0.0f, 0.0f, 0.0f, 0.75f);
  181. glDisable(GL_TEXTURE_2D);
  182. glRecti(0, 0, getWindow().getWidth(), getWindow().getHeight());
  183. glEnable(GL_TEXTURE_2D);
  184. // Draw heading text, using FontString so we can get the
  185. // width of the drawn text to center it
  186. mainText.x = (getWindow().getWidth() / 2) - (mainText.w / 2);
  187. getFont().writeString(mainText);
  188. if (!mMapListFilled) {
  189. getFont().drawText(25, (getWindow().getHeight() / 2) - 20, 0.75f, BLUE, "Generating map list...");
  190. } else {
  191. if (mMapList.size() == 0) {
  192. getFont().drawText(25, (getWindow().getHeight() / 2) - 20, 0.75f, RED, "No maps found! See README.md");
  193. } else {
  194. // draw *play button* above list
  195. glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
  196. glDisable(GL_TEXTURE_2D);
  197. glRecti(25, 25, 100, 75);
  198. glEnable(GL_TEXTURE_2D);
  199. getFont().drawText(40, 35, 0.75f, BLACK, "Play");
  200. displayMapList();
  201. }
  202. }
  203. // Fill map list after first render pass,
  204. // so menu *loading screen* is visible
  205. if (mFirstPass) {
  206. if (!mMapListFilled) {
  207. fillMapList();
  208. }
  209. } else {
  210. mFirstPass = true;
  211. }
  212. }
  213. }
  214. void Menu::play() {
  215. char *tmp = bufferString("load %s", mMapList[mCursor]);
  216. if (getOpenRaider().command(tmp) == 0) {
  217. setVisible(false);
  218. } else {
  219. //! \todo Display something if an error occurs
  220. }
  221. delete [] tmp;
  222. }
  223. void Menu::handleKeyboard(KeyboardButton key, bool pressed) {
  224. if (!pressed)
  225. return;
  226. if (key == upKey) {
  227. if (mCursor > 0)
  228. mCursor--;
  229. else
  230. mCursor = mMapList.size() - 1;
  231. } else if (key == downKey) {
  232. if (mCursor < (mMapList.size() - 1))
  233. mCursor++;
  234. else
  235. mCursor = 0;
  236. } else if (key == rightKey) {
  237. long i = 10;
  238. if (mCursor > (mMapList.size() - 11))
  239. i = mMapList.size() - 1 - mCursor;
  240. while (i-- > 0)
  241. handleKeyboard(downKey, true);
  242. } else if (key == leftKey) {
  243. long i = 10;
  244. if (mCursor < 10)
  245. i = mCursor;
  246. while (i-- > 0)
  247. handleKeyboard(upKey, true);
  248. } else if (key == enterKey) {
  249. play();
  250. }
  251. }
  252. void Menu::handleMouseClick(unsigned int x, unsigned int y, KeyboardButton button, bool released) {
  253. int items = (getWindow().getHeight() - 110) / 25;
  254. if (released || (button != leftmouseKey))
  255. return;
  256. if ((y >= 100) && (y <= (unsigned int)(100 + (25 * items)))) {
  257. y -= 100;
  258. mCursor = mMin + (y / 25);
  259. } else if ((y >= 25) && (y <= 100) && (x >= 25) && (x <= 125)) {
  260. // Play button
  261. play();
  262. }
  263. }