💿🐜 Antkeeper source code https://antkeeper.com
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.

470 lines
12 KiB

  1. /*
  2. * Copyright (C) 2021 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper source code.
  5. *
  6. * Antkeeper source code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper source code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "game/menu.hpp"
  20. #include "scene/text.hpp"
  21. #include "application.hpp"
  22. #include "animation/animation.hpp"
  23. #include "animation/animator.hpp"
  24. #include "animation/ease.hpp"
  25. #include <algorithm>
  26. namespace game {
  27. namespace menu {
  28. void init_menu_item_index(game::context& ctx, const std::string& menu_name)
  29. {
  30. if (auto it = ctx.menu_item_indices.find(menu_name); it != ctx.menu_item_indices.end())
  31. {
  32. ctx.menu_item_index = &it->second;
  33. }
  34. else
  35. {
  36. ctx.menu_item_index = &ctx.menu_item_indices[menu_name];
  37. *ctx.menu_item_index = 0;
  38. }
  39. }
  40. void update_text_font(game::context& ctx)
  41. {
  42. for (auto [name, value]: ctx.menu_item_texts)
  43. {
  44. name->set_material(&ctx.menu_font_material);
  45. name->set_font(&ctx.menu_font);
  46. if (value)
  47. {
  48. value->set_material(&ctx.menu_font_material);
  49. value->set_font(&ctx.menu_font);
  50. }
  51. }
  52. }
  53. void update_text_color(game::context& ctx)
  54. {
  55. for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
  56. {
  57. auto [name, value] = ctx.menu_item_texts[i];
  58. const float4& color = (i == *ctx.menu_item_index) ? active_color : inactive_color;
  59. name->set_color(color);
  60. if (value)
  61. value->set_color(color);
  62. }
  63. }
  64. void update_text_tweens(game::context& ctx)
  65. {
  66. for (auto [name, value]: ctx.menu_item_texts)
  67. {
  68. name->update_tweens();
  69. if (value)
  70. value->update_tweens();
  71. }
  72. }
  73. void align_text(game::context& ctx, bool center, bool has_back, float anchor_y)
  74. {
  75. // Calculate menu width
  76. float menu_width = 0.0f;
  77. float menu_spacing = ctx.menu_font.get_glyph_metrics(U'M').width;
  78. for (auto [name, value]: ctx.menu_item_texts)
  79. {
  80. float row_width = 0.0f;
  81. // Add name width to width
  82. const auto& name_bounds = static_cast<const geom::aabb<float>&>(name->get_local_bounds());
  83. row_width += name_bounds.max_point.x - name_bounds.min_point.x;
  84. if (value)
  85. {
  86. // Add value width to width
  87. //const auto& value_bounds = static_cast<const geom::aabb<float>&>(value->get_local_bounds());
  88. //row_width += value_bounds.max_point.x - value_bounds.min_point.x;
  89. // Add spacing to row width
  90. row_width += menu_spacing * 8.0f;
  91. }
  92. menu_width = std::max<float>(menu_width, row_width);
  93. }
  94. // Align texts
  95. float menu_height;
  96. if (has_back)
  97. menu_height = (ctx.menu_item_texts.size() - 1) * ctx.menu_font.get_font_metrics().linespace - ctx.menu_font.get_font_metrics().linegap;
  98. else
  99. menu_height = ctx.menu_item_texts.size() * ctx.menu_font.get_font_metrics().linespace - ctx.menu_font.get_font_metrics().linegap;
  100. float menu_x = -menu_width * 0.5f;
  101. float menu_y = anchor_y + menu_height * 0.5f - ctx.menu_font.get_font_metrics().size;
  102. for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
  103. {
  104. auto [name, value] = ctx.menu_item_texts[i];
  105. float x = menu_x;
  106. float y = menu_y - ctx.menu_font.get_font_metrics().linespace * i;
  107. if (has_back && i == ctx.menu_item_texts.size() - 1)
  108. y -= ctx.menu_font.get_font_metrics().linespace;
  109. if (center || i == ctx.menu_item_texts.size() - 1)
  110. {
  111. const auto& name_bounds = static_cast<const geom::aabb<float>&>(name->get_local_bounds());
  112. const float name_width = name_bounds.max_point.x - name_bounds.min_point.x;
  113. x = -name_width * 0.5f;
  114. }
  115. name->set_translation({std::round(x), std::round(y), 0.0f});
  116. if (value)
  117. {
  118. const auto& value_bounds = static_cast<const geom::aabb<float>&>(value->get_local_bounds());
  119. const float value_width = value_bounds.max_point.x - value_bounds.min_point.x;
  120. if (center || i == ctx.menu_item_texts.size() - 1)
  121. x = -value_width * 0.5f;
  122. else
  123. x = menu_x + menu_width - value_width;
  124. value->set_translation({std::round(x), std::round(y), 0.0f});
  125. }
  126. }
  127. }
  128. void refresh_text(game::context& ctx)
  129. {
  130. for (auto [name, value]: ctx.menu_item_texts)
  131. {
  132. name->refresh();
  133. if (value)
  134. value->refresh();
  135. }
  136. }
  137. void add_text_to_ui(game::context& ctx)
  138. {
  139. for (auto [name, value]: ctx.menu_item_texts)
  140. {
  141. ctx.ui_scene->add_object(name);
  142. if (value)
  143. ctx.ui_scene->add_object(value);
  144. }
  145. }
  146. void remove_text_from_ui(game::context& ctx)
  147. {
  148. for (auto [name, value]: ctx.menu_item_texts)
  149. {
  150. ctx.ui_scene->remove_object(name);
  151. if (value)
  152. ctx.ui_scene->remove_object(value);
  153. }
  154. }
  155. void delete_text(game::context& ctx)
  156. {
  157. for (auto [name, value]: ctx.menu_item_texts)
  158. {
  159. delete name;
  160. if (value)
  161. delete value;
  162. }
  163. ctx.menu_item_texts.clear();
  164. }
  165. void delete_animations(game::context& ctx)
  166. {
  167. ctx.animator->remove_animation(ctx.menu_fade_animation);
  168. delete ctx.menu_fade_animation;
  169. ctx.menu_fade_animation = nullptr;
  170. }
  171. void clear_callbacks(game::context& ctx)
  172. {
  173. // Clear menu item callbacks
  174. ctx.menu_left_callbacks.clear();
  175. ctx.menu_right_callbacks.clear();
  176. ctx.menu_select_callbacks.clear();
  177. ctx.menu_back_callback = nullptr;
  178. }
  179. void setup_animations(game::context& ctx)
  180. {
  181. ctx.menu_fade_animation = new animation<float>();
  182. animation_channel<float>* opacity_channel = ctx.menu_fade_animation->add_channel(0);
  183. ctx.menu_fade_animation->set_frame_callback
  184. (
  185. [&ctx](int channel, const float& opacity)
  186. {
  187. for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
  188. {
  189. auto [name, value] = ctx.menu_item_texts[i];
  190. float4 color = (i == *ctx.menu_item_index) ? active_color : inactive_color;
  191. color[3] = color[3] * opacity;
  192. if (name)
  193. name->set_color(color);
  194. if (value)
  195. value->set_color(color);
  196. }
  197. }
  198. );
  199. ctx.animator->add_animation(ctx.menu_fade_animation);
  200. }
  201. void fade_in(game::context& ctx, const std::function<void()>& end_callback)
  202. {
  203. ctx.menu_fade_animation->set_interpolator(ease<float>::out_cubic);
  204. animation_channel<float>* opacity_channel = ctx.menu_fade_animation->get_channel(0);
  205. opacity_channel->remove_keyframes();
  206. opacity_channel->insert_keyframe({0.0, 0.0f});
  207. opacity_channel->insert_keyframe({game::menu::fade_in_duration, 1.0f});
  208. ctx.menu_fade_animation->set_end_callback(end_callback);
  209. for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
  210. {
  211. auto [name, value] = ctx.menu_item_texts[i];
  212. float4 color = (i == *ctx.menu_item_index) ? active_color : inactive_color;
  213. color[3] = 0.0f;
  214. if (name)
  215. {
  216. name->set_color(color);
  217. name->update_tweens();
  218. }
  219. if (value)
  220. {
  221. value->set_color(color);
  222. value->update_tweens();
  223. }
  224. }
  225. ctx.menu_fade_animation->stop();
  226. ctx.menu_fade_animation->play();
  227. }
  228. void fade_out(game::context& ctx, const std::function<void()>& end_callback)
  229. {
  230. ctx.menu_fade_animation->set_interpolator(ease<float>::out_cubic);
  231. animation_channel<float>* opacity_channel = ctx.menu_fade_animation->get_channel(0);
  232. opacity_channel->remove_keyframes();
  233. opacity_channel->insert_keyframe({0.0, 1.0f});
  234. opacity_channel->insert_keyframe({game::menu::fade_out_duration, 0.0f});
  235. ctx.menu_fade_animation->set_end_callback(end_callback);
  236. ctx.menu_fade_animation->stop();
  237. ctx.menu_fade_animation->play();
  238. }
  239. void fade_in_bg(game::context& ctx)
  240. {
  241. ctx.menu_bg_fade_out_animation->stop();
  242. ctx.menu_bg_fade_in_animation->stop();
  243. ctx.menu_bg_fade_in_animation->play();
  244. }
  245. void fade_out_bg(game::context& ctx)
  246. {
  247. ctx.menu_bg_fade_in_animation->stop();
  248. ctx.menu_bg_fade_out_animation->stop();
  249. ctx.menu_bg_fade_out_animation->play();
  250. }
  251. void setup_controls(game::context& ctx)
  252. {
  253. ctx.controls["menu_up"]->set_activated_callback
  254. (
  255. [&ctx]()
  256. {
  257. --(*ctx.menu_item_index);
  258. if (*ctx.menu_item_index < 0)
  259. *ctx.menu_item_index = ctx.menu_item_texts.size() - 1;
  260. update_text_color(ctx);
  261. }
  262. );
  263. ctx.controls["menu_down"]->set_activated_callback
  264. (
  265. [&ctx]()
  266. {
  267. ++(*ctx.menu_item_index);
  268. if (*ctx.menu_item_index >= ctx.menu_item_texts.size())
  269. *ctx.menu_item_index = 0;
  270. update_text_color(ctx);
  271. }
  272. );
  273. ctx.controls["menu_left"]->set_activated_callback
  274. (
  275. [&ctx]()
  276. {
  277. auto callback = ctx.menu_left_callbacks[*ctx.menu_item_index];
  278. if (callback != nullptr)
  279. callback();
  280. }
  281. );
  282. ctx.controls["menu_right"]->set_activated_callback
  283. (
  284. [&ctx]()
  285. {
  286. auto callback = ctx.menu_right_callbacks[*ctx.menu_item_index];
  287. if (callback != nullptr)
  288. callback();
  289. }
  290. );
  291. ctx.controls["menu_select"]->set_activated_callback
  292. (
  293. [&ctx]()
  294. {
  295. auto callback = ctx.menu_select_callbacks[*ctx.menu_item_index];
  296. if (callback != nullptr)
  297. callback();
  298. }
  299. );
  300. ctx.controls["menu_back"]->set_activated_callback
  301. (
  302. [&ctx]()
  303. {
  304. if (ctx.menu_back_callback != nullptr)
  305. ctx.menu_back_callback();
  306. }
  307. );
  308. ctx.menu_mouse_tracker->set_mouse_moved_callback
  309. (
  310. [&ctx](const mouse_moved_event& event)
  311. {
  312. const float padding = game::menu::mouseover_padding * ctx.menu_font.get_font_metrics().size;
  313. for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
  314. {
  315. auto [name, value] = ctx.menu_item_texts[i];
  316. const auto& name_bounds = static_cast<const geom::aabb<float>&>(name->get_world_bounds());
  317. float min_x = name_bounds.min_point.x;
  318. float min_y = name_bounds.min_point.y;
  319. float max_x = name_bounds.max_point.x;
  320. float max_y = name_bounds.max_point.y;
  321. if (value)
  322. {
  323. const auto& value_bounds = static_cast<const geom::aabb<float>&>(value->get_world_bounds());
  324. min_x = std::min<float>(min_x, value_bounds.min_point.x);
  325. min_y = std::min<float>(min_y, value_bounds.min_point.y);
  326. max_x = std::max<float>(max_x, value_bounds.max_point.x);
  327. max_y = std::max<float>(max_y, value_bounds.max_point.y);
  328. }
  329. const auto& viewport = ctx.app->get_viewport_dimensions();
  330. const float x = static_cast<float>(event.x - viewport[0] / 2);
  331. const float y = static_cast<float>((viewport[1] - event.y + 1) - viewport[1] / 2);
  332. min_x -= padding;
  333. min_y -= padding;
  334. max_x += padding;
  335. max_y += padding;
  336. if (x >= min_x && x <= max_x)
  337. {
  338. if (y >= min_y && y <= max_y)
  339. {
  340. *ctx.menu_item_index = i;
  341. update_text_color(ctx);
  342. break;
  343. }
  344. }
  345. }
  346. }
  347. );
  348. ctx.menu_mouse_tracker->set_mouse_button_pressed_callback
  349. (
  350. [&ctx](const mouse_button_pressed_event& event)
  351. {
  352. for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
  353. {
  354. auto [name, value] = ctx.menu_item_texts[i];
  355. const auto& name_bounds = static_cast<const geom::aabb<float>&>(name->get_world_bounds());
  356. float min_x = name_bounds.min_point.x;
  357. float min_y = name_bounds.min_point.y;
  358. float max_x = name_bounds.max_point.x;
  359. float max_y = name_bounds.max_point.y;
  360. if (value)
  361. {
  362. const auto& value_bounds = static_cast<const geom::aabb<float>&>(value->get_world_bounds());
  363. min_x = std::min<float>(min_x, value_bounds.min_point.x);
  364. min_y = std::min<float>(min_y, value_bounds.min_point.y);
  365. max_x = std::max<float>(max_x, value_bounds.max_point.x);
  366. max_y = std::max<float>(max_y, value_bounds.max_point.y);
  367. }
  368. const auto& viewport = ctx.app->get_viewport_dimensions();
  369. const float x = static_cast<float>(event.x - viewport[0] / 2);
  370. const float y = static_cast<float>((viewport[1] - event.y + 1) - viewport[1] / 2);
  371. if (x >= min_x && x <= max_x)
  372. {
  373. if (y >= min_y && y <= max_y)
  374. {
  375. *ctx.menu_item_index = i;
  376. update_text_color(ctx);
  377. if (event.button == 1)
  378. {
  379. auto callback = ctx.menu_select_callbacks[i];
  380. if (callback)
  381. callback();
  382. }
  383. else if (event.button == 3)
  384. {
  385. auto callback = ctx.menu_left_callbacks[i];
  386. if (callback)
  387. callback();
  388. }
  389. return;
  390. }
  391. }
  392. }
  393. }
  394. );
  395. }
  396. void clear_controls(game::context& ctx)
  397. {
  398. ctx.controls["menu_up"]->set_activated_callback(nullptr);
  399. ctx.controls["menu_down"]->set_activated_callback(nullptr);
  400. ctx.controls["menu_left"]->set_activated_callback(nullptr);
  401. ctx.controls["menu_right"]->set_activated_callback(nullptr);
  402. ctx.controls["menu_select"]->set_activated_callback(nullptr);
  403. ctx.controls["menu_back"]->set_activated_callback(nullptr);
  404. ctx.menu_mouse_tracker->set_mouse_moved_callback(nullptr);
  405. ctx.menu_mouse_tracker->set_mouse_button_pressed_callback(nullptr);
  406. }
  407. } // namespace menu
  408. } // namespace game