/* * Copyright (C) 2017 Christopher J. Howard * * This file is part of Antkeeper Source Code. * * Antkeeper Source Code is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Antkeeper Source Code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Antkeeper Source Code. If not, see . */ #include "ui.hpp" #include "../application.hpp" // UI callbacks void menuPrint(Application* application, const std::string& string) { std::cout << string << std::endl; } UIElement::UIElement(): parent(nullptr), anchor(Anchor::TOP_LEFT), layerOffset(0), layer(0), origin(0.0f), translation(0.0f), rotation(0.0f), dimensions(0.0f), position(0.0f), bounds(position, position), tintColor(1.0f), color(tintColor), visible(true), active(true), mouseOver(false), mouseOverCallback(nullptr), mouseOutCallback(nullptr), mouseMovedCallback(nullptr), mousePressedCallback(nullptr), mouseReleasedCallback(nullptr) {} UIElement::~UIElement() {} void UIElement::update() { // Calculate position if (parent != nullptr) { // Calculate world-space position Vector2 anchorPoint = parent->position + parent->dimensions * anchor - dimensions * anchor; position = anchorPoint + origin + translation; // Calculate layer layer = parent->layer + 1 + layerOffset; // Calculate color color = parent->color * tintColor; } else { position = origin + translation; layer = layerOffset; color = tintColor; } // Calculate bounds bounds.setMin(position); bounds.setMax(position + dimensions); // Update children for (UIElement* child: children) { child->update(); } } void UIElement::addChild(UIElement* element) { children.push_back(element); element->parent = this; } void UIElement::removeChild(UIElement* element) { for (auto it = children.begin(); it != children.end(); ++it) { if (*it == element) { children.erase(it); return; } } } void UIElement::setMouseOverCallback(std::function callback) { mouseOverCallback = callback; } void UIElement::setMouseOutCallback(std::function callback) { mouseOutCallback = callback; } void UIElement::setMouseMovedCallback(std::function callback) { mouseMovedCallback = callback; } void UIElement::setMousePressedCallback(std::function callback) { mousePressedCallback = callback; } void UIElement::setMouseReleasedCallback(std::function callback) { mouseReleasedCallback = callback; } void UIElement::mouseMoved(int x, int y) { if (!active) { return; } if (bounds.contains(Vector2(x, y))) { if (!mouseOver) { mouseOver = true; if (mouseOverCallback) { mouseOverCallback(); } } if (mouseMovedCallback) { mouseMovedCallback(x, y); } } else if (mouseOver) { mouseOver = false; if (mouseOutCallback) { mouseOutCallback(); } } for (UIElement* child: children) { child->mouseMoved(x, y); } } void UIElement::mouseButtonPressed(int button, int x, int y) { if (!active) { return; } if (bounds.contains(Vector2(x, y))) { if (mousePressedCallback) { mousePressedCallback(button, x, y); } for (UIElement* child: children) { child->mouseButtonPressed(button, x, y); } } } void UIElement::mouseButtonReleased(int button, int x, int y) { if (!active) { return; } if (bounds.contains(Vector2(x, y))) { if (mouseReleasedCallback) { mouseReleasedCallback(button, x , y); } for (UIElement* child: children) { child->mouseButtonReleased(button, x, y); } } } UILabel::UILabel(): font(nullptr) {} UILabel::~UILabel() {} void UILabel::setFont(Font* font) { this->font = font; material.texture = font->getTexture(); calculateDimensions(); } void UILabel::setText(const std::string& text) { this->text = text; calculateDimensions(); } void UILabel::calculateDimensions() { if (font != nullptr && !text.empty()) { float width = font->getWidth(text.c_str()); float height = font->getMetrics().getHeight(); setDimensions(Vector2(width, height)); } else { setDimensions(Vector2(0.0f)); } } UIImage::UIImage(): textureBounds(Vector2(0.0f), Vector2(1.0f)) {} UIImage::~UIImage() {} void UIBatcher::batch(BillboardBatch* result, const UIElement* ui) { // Create list of visible UI elements std::list elements; queueElements(&elements, ui); // Sort UI elements according to layer and texture elements.sort([](const UIElement* a, const UIElement* b) { if (a->getLayer() < b->getLayer()) { return true; } else if (b->getLayer() < a->getLayer()) { return false; } return (a->getMaterial()->texture < b->getMaterial()->texture); }); // Clear previous ranges result->removeRanges(); // Batch UI elements for (const UIElement* element: elements) { batchElement(result, element); } // Update batch result->update(); } void UIBatcher::queueElements(std::list* elements, const UIElement* element) const { if (element->isVisible()) { elements->push_back(element); for (std::size_t i = 0; i < element->getChildCount(); ++i) { queueElements(elements, element->getChild(i)); } } } BillboardBatch::Range* UIBatcher::getRange(BillboardBatch* result, const UIElement* element) const { BillboardBatch::Range* range = nullptr; if (!result->getRangeCount()) { // Create initial range range = result->addRange(); range->material = (Material*)element->getMaterial(); range->start = 0; range->length = 0; } else { range = result->getRange(result->getRangeCount() - 1); const UIMaterial* material = static_cast(range->material); if (material->texture != element->getMaterial()->texture) { // Create new range for the element range = result->addRange(); BillboardBatch::Range* precedingRange = result->getRange(result->getRangeCount() - 2); range->material = (Material*)element->getMaterial(); range->start = precedingRange->start + precedingRange->length; range->length = 0; } } return range; } void UIBatcher::batchElement(BillboardBatch* result, const UIElement* element) { switch (element->getElementType()) { case UIElement::Type::LABEL: batchLabel(result, static_cast(element)); break; case UIElement::Type::IMAGE: batchImage(result, static_cast(element)); break; case UIElement::Type::CONTAINER: break; default: break; } } void UIBatcher::batchLabel(BillboardBatch* result, const UILabel* label) { if (label->getFont() != nullptr && !label->getText().empty()) { // Get range BillboardBatch::Range* range = getRange(result, label); // Pixel-perfect Vector3 origin = Vector3((int)label->getPosition().x, (int)label->getPosition().y, label->getLayer() * 0.01f); // Print billboards const Font* font = label->getFont(); std::size_t index = range->start + range->length; std::size_t count = 0; font->puts(result, origin, label->getText().c_str(), label->getColor(), index, &count); // Increment range length range->length += count; } } void UIBatcher::batchImage(BillboardBatch* result, const UIImage* image) { // Get range BillboardBatch::Range* range = getRange(result, image); // Pixel-perfect //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); Vector3 translation = Vector3(image->getPosition() + image->getDimensions() * 0.5f, image->getLayer() * 0.01f); // Create billboard std::size_t index = range->start + range->length; Billboard* billboard = result->getBillboard(index); billboard->setDimensions(image->getDimensions()); billboard->setTranslation(translation); if (image->getRotation() != 0.0f) { billboard->setRotation(glm::angleAxis(image->getRotation(), Vector3(0, 0, -1.0f))); } billboard->setTextureCoordinates(image->getTextureBounds().getMin(), image->getTextureBounds().getMax()); billboard->setTintColor(image->getColor()); // Increment range length ++(range->length); } MenuItem::MenuItem(Menu* parent, std::size_t index): parent(parent), index(index), selected(false), selectedCallback(nullptr), deselectedCallback(nullptr), activatedCallback(nullptr) {} void MenuItem::select() { if (!selected) { selected = true; if (selectedCallback != nullptr) { selectedCallback(); } } } void MenuItem::deselect() { if (selected) { selected = false; if (deselectedCallback != nullptr) { deselectedCallback(); } } } void MenuItem::activate() { if (activatedCallback != nullptr) { activatedCallback(); } } void MenuItem::setSelectedCallback(std::function callback) { this->selectedCallback = callback = callback; } void MenuItem::setDeselectedCallback(std::function callback) { this->deselectedCallback = callback; } void MenuItem::setActivatedCallback(std::function callback) { this->activatedCallback = callback; } Menu::Menu(): enteredCallback(nullptr), exitedCallback(nullptr) {} Menu::~Menu() { for (MenuItem* item: items) { delete item; } } void Menu::enter() { if (enteredCallback != nullptr) { enteredCallback(); } } void Menu::exit() { if (exitedCallback != nullptr) { exitedCallback(); } } MenuItem* Menu::addItem() { MenuItem* item = new MenuItem(this, items.size()); items.push_back(item); return item; } void Menu::removeItems() { for (MenuItem* item: items) { delete item; } items.clear(); } void Menu::setEnteredCallback(std::function callback) { this->enteredCallback = callback; } void Menu::setExitedCallback(std::function callback) { this->exitedCallback = callback; }