💿🐜 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.

512 lines
10 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. /*
  2. * Copyright (C) 2017 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 "ui.hpp"
  20. #include "../application.hpp"
  21. // UI callbacks
  22. void menuPrint(Application* application, const std::string& string)
  23. {
  24. std::cout << string << std::endl;
  25. }
  26. UIElement::UIElement():
  27. parent(nullptr),
  28. anchor(Anchor::TOP_LEFT),
  29. layerOffset(0),
  30. layer(0),
  31. origin(0.0f),
  32. translation(0.0f),
  33. rotation(0.0f),
  34. dimensions(0.0f),
  35. position(0.0f),
  36. bounds(position, position),
  37. tintColor(1.0f),
  38. color(tintColor),
  39. visible(true),
  40. active(true),
  41. mouseOver(false),
  42. mouseOverCallback(nullptr),
  43. mouseOutCallback(nullptr),
  44. mouseMovedCallback(nullptr),
  45. mousePressedCallback(nullptr),
  46. mouseReleasedCallback(nullptr)
  47. {}
  48. UIElement::~UIElement()
  49. {}
  50. void UIElement::update()
  51. {
  52. // Calculate position
  53. if (parent != nullptr)
  54. {
  55. // Calculate world-space position
  56. Vector2 anchorPoint = parent->position + parent->dimensions * anchor - dimensions * anchor;
  57. position = anchorPoint + origin + translation;
  58. // Calculate layer
  59. layer = parent->layer + 1 + layerOffset;
  60. // Calculate color
  61. color = parent->color * tintColor;
  62. }
  63. else
  64. {
  65. position = origin + translation;
  66. layer = layerOffset;
  67. color = tintColor;
  68. }
  69. // Calculate bounds
  70. bounds.setMin(position);
  71. bounds.setMax(position + dimensions);
  72. // Update children
  73. for (UIElement* child: children)
  74. {
  75. child->update();
  76. }
  77. }
  78. void UIElement::addChild(UIElement* element)
  79. {
  80. children.push_back(element);
  81. element->parent = this;
  82. }
  83. void UIElement::removeChild(UIElement* element)
  84. {
  85. for (auto it = children.begin(); it != children.end(); ++it)
  86. {
  87. if (*it == element)
  88. {
  89. children.erase(it);
  90. return;
  91. }
  92. }
  93. }
  94. void UIElement::setMouseOverCallback(std::function<void()> callback)
  95. {
  96. mouseOverCallback = callback;
  97. }
  98. void UIElement::setMouseOutCallback(std::function<void()> callback)
  99. {
  100. mouseOutCallback = callback;
  101. }
  102. void UIElement::setMouseMovedCallback(std::function<void(int, int)> callback)
  103. {
  104. mouseMovedCallback = callback;
  105. }
  106. void UIElement::setMousePressedCallback(std::function<void(int, int, int)> callback)
  107. {
  108. mousePressedCallback = callback;
  109. }
  110. void UIElement::setMouseReleasedCallback(std::function<void(int, int, int)> callback)
  111. {
  112. mouseReleasedCallback = callback;
  113. }
  114. void UIElement::mouseMoved(int x, int y)
  115. {
  116. if (!active)
  117. {
  118. return;
  119. }
  120. if (bounds.contains(Vector2(x, y)))
  121. {
  122. if (!mouseOver)
  123. {
  124. mouseOver = true;
  125. if (mouseOverCallback)
  126. {
  127. mouseOverCallback();
  128. }
  129. }
  130. if (mouseMovedCallback)
  131. {
  132. mouseMovedCallback(x, y);
  133. }
  134. }
  135. else if (mouseOver)
  136. {
  137. mouseOver = false;
  138. if (mouseOutCallback)
  139. {
  140. mouseOutCallback();
  141. }
  142. }
  143. for (UIElement* child: children)
  144. {
  145. child->mouseMoved(x, y);
  146. }
  147. }
  148. void UIElement::mouseButtonPressed(int button, int x, int y)
  149. {
  150. if (!active)
  151. {
  152. return;
  153. }
  154. if (bounds.contains(Vector2(x, y)))
  155. {
  156. if (mousePressedCallback)
  157. {
  158. mousePressedCallback(button, x, y);
  159. }
  160. for (UIElement* child: children)
  161. {
  162. child->mouseButtonPressed(button, x, y);
  163. }
  164. }
  165. }
  166. void UIElement::mouseButtonReleased(int button, int x, int y)
  167. {
  168. if (!active)
  169. {
  170. return;
  171. }
  172. if (bounds.contains(Vector2(x, y)))
  173. {
  174. if (mouseReleasedCallback)
  175. {
  176. mouseReleasedCallback(button, x , y);
  177. }
  178. for (UIElement* child: children)
  179. {
  180. child->mouseButtonReleased(button, x, y);
  181. }
  182. }
  183. }
  184. UILabel::UILabel():
  185. font(nullptr)
  186. {}
  187. UILabel::~UILabel()
  188. {}
  189. void UILabel::setFont(Font* font)
  190. {
  191. this->font = font;
  192. material.texture = font->getTexture();
  193. calculateDimensions();
  194. }
  195. void UILabel::setText(const std::string& text)
  196. {
  197. this->text = text;
  198. calculateDimensions();
  199. }
  200. void UILabel::calculateDimensions()
  201. {
  202. if (font != nullptr && !text.empty())
  203. {
  204. float width = font->getWidth(text.c_str());
  205. float height = font->getMetrics().getHeight();
  206. setDimensions(Vector2(width, height));
  207. }
  208. else
  209. {
  210. setDimensions(Vector2(0.0f));
  211. }
  212. }
  213. UIImage::UIImage():
  214. textureBounds(Vector2(0.0f), Vector2(1.0f))
  215. {}
  216. UIImage::~UIImage()
  217. {}
  218. void UIBatcher::batch(BillboardBatch* result, const UIElement* ui)
  219. {
  220. // Create list of visible UI elements
  221. std::list<const UIElement*> elements;
  222. queueElements(&elements, ui);
  223. // Sort UI elements according to layer and texture
  224. elements.sort([](const UIElement* a, const UIElement* b)
  225. {
  226. if (a->getLayer() < b->getLayer())
  227. {
  228. return true;
  229. }
  230. else if (b->getLayer() < a->getLayer())
  231. {
  232. return false;
  233. }
  234. return (a->getMaterial()->texture < b->getMaterial()->texture);
  235. });
  236. // Clear previous ranges
  237. result->removeRanges();
  238. // Batch UI elements
  239. for (const UIElement* element: elements)
  240. {
  241. batchElement(result, element);
  242. }
  243. // Update batch
  244. result->update();
  245. }
  246. void UIBatcher::queueElements(std::list<const UIElement*>* elements, const UIElement* element) const
  247. {
  248. if (element->isVisible())
  249. {
  250. elements->push_back(element);
  251. for (std::size_t i = 0; i < element->getChildCount(); ++i)
  252. {
  253. queueElements(elements, element->getChild(i));
  254. }
  255. }
  256. }
  257. BillboardBatch::Range* UIBatcher::getRange(BillboardBatch* result, const UIElement* element) const
  258. {
  259. BillboardBatch::Range* range = nullptr;
  260. if (!result->getRangeCount())
  261. {
  262. // Create initial range
  263. range = result->addRange();
  264. range->material = (Material*)element->getMaterial();
  265. range->start = 0;
  266. range->length = 0;
  267. }
  268. else
  269. {
  270. range = result->getRange(result->getRangeCount() - 1);
  271. const UIMaterial* material = static_cast<UIMaterial*>(range->material);
  272. if (material->texture != element->getMaterial()->texture)
  273. {
  274. // Create new range for the element
  275. range = result->addRange();
  276. BillboardBatch::Range* precedingRange = result->getRange(result->getRangeCount() - 2);
  277. range->material = (Material*)element->getMaterial();
  278. range->start = precedingRange->start + precedingRange->length;
  279. range->length = 0;
  280. }
  281. }
  282. return range;
  283. }
  284. void UIBatcher::batchElement(BillboardBatch* result, const UIElement* element)
  285. {
  286. switch (element->getElementType())
  287. {
  288. case UIElement::Type::LABEL:
  289. batchLabel(result, static_cast<const UILabel*>(element));
  290. break;
  291. case UIElement::Type::IMAGE:
  292. batchImage(result, static_cast<const UIImage*>(element));
  293. break;
  294. case UIElement::Type::CONTAINER:
  295. break;
  296. default:
  297. break;
  298. }
  299. }
  300. void UIBatcher::batchLabel(BillboardBatch* result, const UILabel* label)
  301. {
  302. if (label->getFont() != nullptr && !label->getText().empty())
  303. {
  304. // Get range
  305. BillboardBatch::Range* range = getRange(result, label);
  306. // Pixel-perfect
  307. Vector3 origin = Vector3((int)label->getPosition().x, (int)label->getPosition().y, label->getLayer() * 0.01f);
  308. // Print billboards
  309. const Font* font = label->getFont();
  310. std::size_t index = range->start + range->length;
  311. std::size_t count = 0;
  312. font->puts(result, origin, label->getText().c_str(), label->getColor(), index, &count);
  313. // Increment range length
  314. range->length += count;
  315. }
  316. }
  317. void UIBatcher::batchImage(BillboardBatch* result, const UIImage* image)
  318. {
  319. // Get range
  320. BillboardBatch::Range* range = getRange(result, image);
  321. // Pixel-perfect
  322. //Vector3 translation = Vector3((int)(image->getPosition().x + image->getDimensions().x * 0.5f), (int)(image->getPosition().y + image->getDimensions().y * 0.5f), image->getLayer() * 0.01f);
  323. Vector3 translation = Vector3(image->getPosition() + image->getDimensions() * 0.5f, image->getLayer() * 0.01f);
  324. // Create billboard
  325. std::size_t index = range->start + range->length;
  326. Billboard* billboard = result->getBillboard(index);
  327. billboard->setDimensions(image->getDimensions());
  328. billboard->setTranslation(translation);
  329. if (image->getRotation() != 0.0f)
  330. {
  331. billboard->setRotation(glm::angleAxis(image->getRotation(), Vector3(0, 0, -1.0f)));
  332. }
  333. billboard->setTextureCoordinates(image->getTextureBounds().getMin(), image->getTextureBounds().getMax());
  334. billboard->setTintColor(image->getColor());
  335. // Increment range length
  336. ++(range->length);
  337. }
  338. MenuItem::MenuItem(Menu* parent, std::size_t index):
  339. parent(parent),
  340. index(index),
  341. selected(false),
  342. selectedCallback(nullptr),
  343. deselectedCallback(nullptr),
  344. activatedCallback(nullptr)
  345. {}
  346. void MenuItem::select()
  347. {
  348. if (!selected)
  349. {
  350. selected = true;
  351. if (selectedCallback != nullptr)
  352. {
  353. selectedCallback();
  354. }
  355. }
  356. }
  357. void MenuItem::deselect()
  358. {
  359. if (selected)
  360. {
  361. selected = false;
  362. if (deselectedCallback != nullptr)
  363. {
  364. deselectedCallback();
  365. }
  366. }
  367. }
  368. void MenuItem::activate()
  369. {
  370. if (activatedCallback != nullptr)
  371. {
  372. activatedCallback();
  373. }
  374. }
  375. void MenuItem::setSelectedCallback(std::function<void()> callback)
  376. {
  377. this->selectedCallback = callback = callback;
  378. }
  379. void MenuItem::setDeselectedCallback(std::function<void()> callback)
  380. {
  381. this->deselectedCallback = callback;
  382. }
  383. void MenuItem::setActivatedCallback(std::function<void()> callback)
  384. {
  385. this->activatedCallback = callback;
  386. }
  387. Menu::Menu():
  388. enteredCallback(nullptr),
  389. exitedCallback(nullptr)
  390. {}
  391. Menu::~Menu()
  392. {
  393. for (MenuItem* item: items)
  394. {
  395. delete item;
  396. }
  397. }
  398. void Menu::enter()
  399. {
  400. if (enteredCallback != nullptr)
  401. {
  402. enteredCallback();
  403. }
  404. }
  405. void Menu::exit()
  406. {
  407. if (exitedCallback != nullptr)
  408. {
  409. exitedCallback();
  410. }
  411. }
  412. MenuItem* Menu::addItem()
  413. {
  414. MenuItem* item = new MenuItem(this, items.size());
  415. items.push_back(item);
  416. return item;
  417. }
  418. void Menu::removeItems()
  419. {
  420. for (MenuItem* item: items)
  421. {
  422. delete item;
  423. }
  424. items.clear();
  425. }
  426. void Menu::setEnteredCallback(std::function<void()> callback)
  427. {
  428. this->enteredCallback = callback;
  429. }
  430. void Menu::setExitedCallback(std::function<void()> callback)
  431. {
  432. this->exitedCallback = callback;
  433. }