/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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<void()> callback)
|
|
{
|
|
mouseOverCallback = callback;
|
|
}
|
|
|
|
void UIElement::setMouseOutCallback(std::function<void()> callback)
|
|
{
|
|
mouseOutCallback = callback;
|
|
}
|
|
|
|
void UIElement::setMouseMovedCallback(std::function<void(int, int)> callback)
|
|
{
|
|
mouseMovedCallback = callback;
|
|
}
|
|
|
|
void UIElement::setMousePressedCallback(std::function<void(int, int, int)> callback)
|
|
{
|
|
mousePressedCallback = callback;
|
|
}
|
|
|
|
void UIElement::setMouseReleasedCallback(std::function<void(int, int, int)> 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<const UIElement*> 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<const UIElement*>* 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<UIMaterial*>(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<const UILabel*>(element));
|
|
break;
|
|
|
|
case UIElement::Type::IMAGE:
|
|
batchImage(result, static_cast<const UIImage*>(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);
|
|
}
|