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

/*
* 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);
}
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<void()> callback)
{
this->selectedCallback = callback = callback;
}
void MenuItem::setDeselectedCallback(std::function<void()> callback)
{
this->deselectedCallback = callback;
}
void MenuItem::setActivatedCallback(std::function<void()> 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<void()> callback)
{
this->enteredCallback = callback;
}
void Menu::setExitedCallback(std::function<void()> callback)
{
this->exitedCallback = callback;
}