Textbox.cpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. #include "Textbox.h"
  2. #include <cstring>
  3. #include <cstdlib>
  4. #include "utility/misc.h"
  5. #include "input/Input.h"
  6. using WalrusRPG::MAGIC_TOKEN;
  7. using WalrusRPG::COMMAND_LEGNTH;
  8. using WalrusRPG::Textbox;
  9. using WalrusRPG::TextboxState;
  10. using WalrusRPG::TextboxChar;
  11. using WalrusRPG::Graphics::Font;
  12. using WalrusRPG::Graphics::CharacterParameters;
  13. using WalrusRPG::Graphics::Pixel;
  14. using WalrusRPG::Input::Key;
  15. using WalrusRPG::Utils::Rect;
  16. namespace
  17. {
  18. /**
  19. * This is a replacment of strlen to allow skipping tokens as they might
  20. * contain null-terminating characters, making strlen stop earlier than
  21. * planned.
  22. */
  23. signed strlen_tokens(const char *str)
  24. {
  25. signed len = 0;
  26. for (; str[len]; ++len)
  27. {
  28. if (str[len] == MAGIC_TOKEN)
  29. len += COMMAND_LEGNTH;
  30. }
  31. return len;
  32. }
  33. }
  34. Textbox::Textbox(Rect dimensions, Font fnt)
  35. : fnt(fnt), buffer(0), buffer_index(-1), global_string_offset(0),
  36. current_color(0, 0, 0), letter_wait(0), letter_wait_cooldown(10),
  37. dimensions(dimensions), state(Waiting)
  38. {
  39. }
  40. Textbox::Textbox(Font fnt) : Textbox({4, 4, 220, 32}, fnt)
  41. {
  42. }
  43. Textbox::~Textbox()
  44. {
  45. }
  46. void Textbox::set_text(char *new_text)
  47. {
  48. // Clearing the state variables.
  49. letter_wait = 0;
  50. letter_wait_cooldown = 10;
  51. buffer_index = -1;
  52. global_string_offset = 0;
  53. nb_line_to_update = 0;
  54. for (unsigned i = 0; i < nb_lines; ++i)
  55. {
  56. line_nb_characters[i] = 0;
  57. line_widths[i] = 0;
  58. }
  59. buffer.clear();
  60. // Parsing the passed string into a token list.
  61. // TODO : Convert the vector into a dynamically allocated array?
  62. for (signed i = 0; i < strlen_tokens(new_text); ++i)
  63. {
  64. TextboxChar t;
  65. if (new_text[i] == MAGIC_TOKEN)
  66. {
  67. t.c = MAGIC_TOKEN;
  68. t.routine = new_text[i + 1];
  69. t.arg1 = new_text[i + 2];
  70. t.arg2 = new_text[i + 3];
  71. t.arg3 = new_text[i + 4];
  72. i += COMMAND_LEGNTH;
  73. }
  74. else
  75. {
  76. t.c = new_text[i];
  77. t.routine = 0;
  78. t.arg1 = 0;
  79. t.arg2 = 0;
  80. t.arg3 = 0;
  81. }
  82. buffer.push_back(t);
  83. }
  84. state = Updating;
  85. }
  86. /**
  87. * Makes the text box advance of one or more characters/tokens.
  88. */
  89. void Textbox::add_letter(unsigned nb_letters)
  90. {
  91. if (state != Updating || buffer.size() <= 0)
  92. return;
  93. // Mmh, you who enters here, try to forget how the core logic is programmed.
  94. // Myself don't have a frigging clue on how to clean it but it *works*.
  95. // If you ever clean it, I'll be eternally thankful :-°
  96. // Actually, it works as it does right now, but the signedness is messy as hell
  97. // and changing it would most likely break it everywhere.
  98. for (unsigned i = 0;
  99. (i < nb_letters) &&
  100. (buffer_index < 0 || buffer_index < static_cast<signed>(buffer.size()) - 1);
  101. ++i)
  102. {
  103. // As the index starts with -1, increment it before doing anything.
  104. ++buffer_index;
  105. // Parsing commands.
  106. if (buffer[buffer_index].c == MAGIC_TOKEN)
  107. {
  108. switch (buffer[buffer_index].routine)
  109. {
  110. // wait a bit
  111. case 0x81:
  112. letter_wait = buffer[buffer_index].arg1;
  113. break;
  114. }
  115. line_nb_characters[nb_line_to_update]++;
  116. }
  117. else
  118. {
  119. // The frigging static cast...
  120. CharacterParameters &p =
  121. fnt.chars[static_cast<signed>(buffer[buffer_index].c)];
  122. TextboxChar &t = buffer[buffer_index];
  123. // Manual line-return.
  124. if (t.c == '\n')
  125. {
  126. if (nb_line_to_update + 1 >= nb_lines)
  127. {
  128. // No need to go back in the array. Just have to go forward.
  129. line_nb_characters[nb_line_to_update]++;
  130. state = Full;
  131. return;
  132. }
  133. nb_line_to_update++;
  134. }
  135. // If adding the character would make the line too big for the text_box
  136. if (line_widths[nb_line_to_update] + p.dimensions.width + 1 >
  137. dimensions.width)
  138. {
  139. if (nb_line_to_update + 1 >= nb_lines)
  140. {
  141. // Here to avoid getting to lose that character (or the last ones)
  142. // We have to put the reading head one character backwards.
  143. --buffer_index;
  144. state = Full;
  145. return;
  146. }
  147. nb_line_to_update++;
  148. }
  149. // Just adding the correct space width
  150. if (t.c == ' ')
  151. line_widths[nb_line_to_update] += fnt.space_width;
  152. else
  153. line_widths[nb_line_to_update] += p.dimensions.width + 1;
  154. // Putting the parsed character in the current text line.
  155. line_nb_characters[nb_line_to_update]++;
  156. }
  157. }
  158. // Check if the text box finished its work
  159. if (buffer_index >= static_cast<signed>(buffer.size() - 1))
  160. {
  161. state = Done;
  162. }
  163. // You prefer having to wait for characters, no?
  164. letter_wait = letter_wait_cooldown;
  165. }
  166. void Textbox::update(unsigned dt)
  167. {
  168. // Small state machine.
  169. switch (state)
  170. {
  171. case Waiting:
  172. return;
  173. break;
  174. case Updating:
  175. // Time-based update.
  176. if ((buffer_index >= 0) &&
  177. (buffer_index >= static_cast<signed>(buffer.size())))
  178. return;
  179. letter_wait -= dt;
  180. if (letter_wait <= 0)
  181. {
  182. unsigned add = (-letter_wait) / letter_wait_cooldown + 1;
  183. add_letter(add);
  184. }
  185. break;
  186. case Full:
  187. // TODO?: Change the trigger (button) into something else (like a function)?
  188. if (key_pressed(Key::K_A))
  189. {
  190. for (unsigned i = 0; i < nb_lines - 1; ++i)
  191. global_string_offset += line_nb_characters[i];
  192. line_widths[0] = line_widths[nb_lines - 1];
  193. line_nb_characters[0] = line_nb_characters[nb_lines - 1];
  194. for (unsigned i = 1; i < nb_lines; ++i)
  195. {
  196. line_widths[i] = 0;
  197. line_nb_characters[i] = 0;
  198. }
  199. nb_line_to_update = 1;
  200. state = Updating;
  201. }
  202. default:
  203. break;
  204. }
  205. }
  206. void Textbox::render(unsigned dt)
  207. {
  208. UNUSED(dt);
  209. if (buffer_index < 0)
  210. return;
  211. // TODO : store the last character's color to correctly reapply it if a line return
  212. // happens?
  213. current_color = 0xFFFF;
  214. put_rectangle(dimensions, Graphics::Black);
  215. unsigned global_index = global_string_offset;
  216. for (unsigned l = 0; l < nb_lines; l++)
  217. {
  218. unsigned cur_x = dimensions.x;
  219. unsigned cur_y = dimensions.y + l * fnt.baseline;
  220. for (unsigned line_index = 0; line_index < line_nb_characters[l]; ++line_index)
  221. {
  222. TextboxChar b = buffer[global_index + line_index];
  223. char c = b.c;
  224. if (c == MAGIC_TOKEN)
  225. {
  226. switch (b.routine)
  227. {
  228. // Change current color
  229. case 0x01:
  230. current_color = ((b.arg1 << 8) + b.arg2);
  231. break;
  232. }
  233. continue;
  234. }
  235. fnt.draw(cur_x, cur_y, c, current_color);
  236. // *shrugs*
  237. if (c == '\n')
  238. continue;
  239. else if (c == ' ')
  240. cur_x += fnt.space_width;
  241. else
  242. cur_x += fnt.chars[static_cast<signed>(c)].dimensions.width + 1;
  243. }
  244. global_index += line_nb_characters[l];
  245. }
  246. // State indicator.
  247. Pixel indicator_color = Graphics::Black;
  248. if (state == Full)
  249. indicator_color = Graphics::Red;
  250. else if (state == Done)
  251. indicator_color = Graphics::Blue;
  252. if (indicator_color != Graphics::Black)
  253. put_rectangle({dimensions.x + static_cast<signed>(dimensions.width) - 3,
  254. dimensions.y + static_cast<signed>(dimensions.height) - 3, 3, 3},
  255. indicator_color);
  256. }