intake_num.c 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /* MegaZeux
  2. *
  3. * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. #include <math.h>
  20. #include "core.h"
  21. #include "event.h"
  22. #include "graphics.h"
  23. #include "intake_num.h"
  24. #include "util.h"
  25. #include "window.h"
  26. struct intake_num_context
  27. {
  28. context ctx;
  29. int x;
  30. int y;
  31. int w;
  32. int color;
  33. int value;
  34. int min_val;
  35. int max_val;
  36. boolean leading_minus;
  37. boolean empty;
  38. boolean save;
  39. context *callback_ctx;
  40. void (*callback_fn)(context *, int);
  41. };
  42. /**
  43. * Change the input number and bound it.
  44. */
  45. static void change_value(struct intake_num_context *intk, int value)
  46. {
  47. if(intk->leading_minus && value)
  48. value *= -1;
  49. intk->value = CLAMP(value, intk->min_val, intk->max_val);
  50. intk->empty = false;
  51. intk->leading_minus = false;
  52. }
  53. /**
  54. * Expand draw area to encompass maximum/minimum value if necessary.
  55. * The minus is drawn on the left border char, so abs() is used to ignore it.
  56. */
  57. static void fix_draw_area(struct intake_num_context *intk)
  58. {
  59. char buffer[12];
  60. int buf_len;
  61. snprintf(buffer, 12, "%d", abs(intk->max_val));
  62. buf_len = strlen(buffer);
  63. if(buf_len > intk->w)
  64. intk->w = buf_len;
  65. snprintf(buffer, 12, "%d", abs(intk->min_val));
  66. buf_len = strlen(buffer);
  67. if(buf_len > intk->w)
  68. intk->w = buf_len;
  69. }
  70. /**
  71. * Draw the number input to the screen.
  72. */
  73. static void intake_num_draw(context *ctx)
  74. {
  75. struct intake_num_context *intk = (struct intake_num_context *)ctx;
  76. int write_pos = intk->x + intk->w + 1;
  77. char buffer[12] = { 0 };
  78. int i;
  79. snprintf(buffer, 12, "%d", intk->value);
  80. write_pos -= strlen(buffer);
  81. for(i = 0; i < intk->w + 2; i++)
  82. draw_char(0, intk->color, intk->x + i, intk->y);
  83. if(intk->leading_minus)
  84. draw_char('-', intk->color, write_pos, intk->y);
  85. if(!intk->empty)
  86. write_string(buffer, write_pos, intk->y, intk->color, false);
  87. }
  88. /**
  89. * Keyhandler for number input.
  90. */
  91. static boolean intake_num_key(context *ctx, int *key)
  92. {
  93. struct intake_num_context *intk = (struct intake_num_context *)ctx;
  94. int increment_value = 0;
  95. if(get_exit_status())
  96. *key = IKEY_ESCAPE;
  97. if((*key >= IKEY_0) && (*key <= IKEY_9))
  98. {
  99. // At exactly maximum/minimum, typing a number should wrap
  100. if((intk->value == intk->min_val && intk->min_val < 0)
  101. || (intk->value == intk->max_val && intk->max_val > 0))
  102. {
  103. change_value(intk, 0);
  104. }
  105. else
  106. {
  107. int add_value = (*key - IKEY_0);
  108. if(intk->value < 0)
  109. add_value *= -1;
  110. change_value(intk, intk->value * 10 + add_value);
  111. }
  112. return true;
  113. }
  114. switch(*key)
  115. {
  116. case IKEY_ESCAPE:
  117. {
  118. // Exit
  119. intk->save = false;
  120. destroy_context(ctx);
  121. return true;
  122. }
  123. case IKEY_RETURN:
  124. case IKEY_TAB:
  125. {
  126. // Set component and exit
  127. destroy_context(ctx);
  128. return true;
  129. }
  130. case IKEY_DELETE:
  131. case IKEY_PERIOD:
  132. {
  133. // Set to zero and exit
  134. change_value(intk, 0);
  135. destroy_context(ctx);
  136. return true;
  137. }
  138. case IKEY_BACKSPACE:
  139. {
  140. int new_value = intk->value / 10;
  141. if(intk->leading_minus)
  142. {
  143. intk->leading_minus = false;
  144. }
  145. else
  146. if(new_value != 0)
  147. {
  148. change_value(intk, new_value);
  149. }
  150. else
  151. {
  152. if(intk->value < 0)
  153. intk->leading_minus = true;
  154. intk->value = 0;
  155. intk->empty = true;
  156. }
  157. return true;
  158. }
  159. case IKEY_MINUS:
  160. {
  161. if(intk->min_val < 0)
  162. {
  163. // Add/remove leading '-'
  164. if(intk->empty || intk->value == 0)
  165. {
  166. intk->empty = 1;
  167. intk->leading_minus ^= 1;
  168. }
  169. else
  170. // Negate the existing number
  171. if(-intk->value >= intk->min_val && -intk->value <= intk->max_val)
  172. {
  173. change_value(intk, intk->value * -1);
  174. }
  175. }
  176. return true;
  177. }
  178. case IKEY_HOME:
  179. {
  180. intk->value = intk->min_val;
  181. return true;
  182. }
  183. case IKEY_END:
  184. {
  185. intk->value = intk->max_val;
  186. return true;
  187. }
  188. case IKEY_RIGHT:
  189. case IKEY_UP:
  190. {
  191. if(get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal))
  192. {
  193. increment_value = 10;
  194. }
  195. else
  196. {
  197. increment_value = 1;
  198. }
  199. break;
  200. }
  201. case IKEY_PAGEUP:
  202. {
  203. increment_value = 100;
  204. break;
  205. }
  206. case IKEY_LEFT:
  207. case IKEY_DOWN:
  208. {
  209. if(get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal))
  210. {
  211. increment_value = -10;
  212. }
  213. else
  214. {
  215. increment_value = -1;
  216. }
  217. break;
  218. }
  219. case IKEY_PAGEDOWN:
  220. {
  221. increment_value = -100;
  222. break;
  223. }
  224. }
  225. if(increment_value)
  226. {
  227. change_value(intk, intk->value + increment_value);
  228. return true;
  229. }
  230. return false;
  231. }
  232. /**
  233. * New mouse clicks exit and save the new value.
  234. */
  235. static boolean intake_num_click(context *ctx, int *key, int button,
  236. int x, int y)
  237. {
  238. if(button && !get_mouse_drag())
  239. {
  240. destroy_context(ctx);
  241. return true;
  242. }
  243. return false;
  244. }
  245. /**
  246. * On exit, get the final value and execute the callback if necessary.
  247. */
  248. static void intake_num_destroy(context *ctx)
  249. {
  250. struct intake_num_context *intk = (struct intake_num_context *)ctx;
  251. if(intk->save)
  252. {
  253. int value = CLAMP(intk->value, intk->min_val, intk->max_val);
  254. if(intk->callback_fn)
  255. intk->callback_fn(intk->callback_ctx, value);
  256. }
  257. }
  258. /**
  259. * Create a context for number entry. On exit if the number was changed, the
  260. * callback will be executed using the parent context and the final value as
  261. * inputs.
  262. */
  263. context *intake_num(context *parent, int value, int min_val, int max_val,
  264. int x, int y, int min_width, int color, void (*callback)(context *, int))
  265. {
  266. struct intake_num_context *intk = cmalloc(sizeof(struct intake_num_context));
  267. struct context_spec spec;
  268. intk->x = x;
  269. intk->y = y;
  270. intk->w = min_width;
  271. intk->color = color;
  272. intk->value = value;
  273. intk->min_val = min_val;
  274. intk->max_val = max_val;
  275. intk->leading_minus = false;
  276. intk->empty = false;
  277. intk->save = true;
  278. intk->callback_ctx = parent;
  279. intk->callback_fn = callback;
  280. fix_draw_area(intk);
  281. memset(&spec, 0, sizeof(struct context_spec));
  282. spec.draw = intake_num_draw;
  283. spec.key = intake_num_key;
  284. spec.click = intake_num_click;
  285. spec.destroy = intake_num_destroy;
  286. spec.framerate_mode = FRAMERATE_UI_INTERRUPT;
  287. create_context((context *)intk, parent, &spec, CTX_INTAKE_NUM);
  288. return (context *)intk;
  289. }