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

505 lines
9.8 KiB

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