ES-DE/es-core/src/components/NinePatchComponent.cpp

194 lines
6.8 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// NinePatchComponent.cpp
//
// Breaks up an image into 3x3 patches to accomodate resizing without distortions.
//
#include "components/NinePatchComponent.h"
2017-11-01 22:21:10 +00:00
#include "Log.h"
#include "ThemeData.h"
#include "resources/TextureResource.h"
NinePatchComponent::NinePatchComponent(Window* window,
const std::string& path,
unsigned int edgeColor,
unsigned int centerColor)
: GuiComponent(window)
, mCornerSize(16.0f, 16.0f)
, mEdgeColor(edgeColor)
, mCenterColor(centerColor)
, mPath(path)
, mVertices(nullptr)
{
if (!mPath.empty())
buildVertices();
}
NinePatchComponent::~NinePatchComponent()
{
if (mVertices != nullptr)
delete[] mVertices;
}
void NinePatchComponent::updateColors()
{
const unsigned int edgeColor = Renderer::convertRGBAToABGR(mEdgeColor);
const unsigned int centerColor = Renderer::convertRGBAToABGR(mCenterColor);
for (int i = 0; i < 6 * 9; i++)
mVertices[i].col = edgeColor;
for (int i = 6 * 4; i < 6; i++)
mVertices[(6 * 4) + i].col = centerColor;
}
void NinePatchComponent::buildVertices()
{
if (mVertices != nullptr)
delete[] mVertices;
// Scale the corner size relative to the screen resolution, but keep the scale factor
// within reason as extreme resolutions may cause artifacts. Any "normal" resolution
// (e.g. from 720p to 4K) will be within these boundaries though.
float scaleFactor;
if (Renderer::getScreenWidth() > Renderer::getScreenHeight())
scaleFactor = Math::clamp(Renderer::getScreenHeightModifier(), 0.4f, 3.0f);
else
scaleFactor = Math::clamp(Renderer::getScreenWidthModifier(), 0.4f, 3.0f);
mTexture = TextureResource::get(mPath, false, false, true, scaleFactor);
if (mTexture->getSize() == Vector2i::Zero()) {
mVertices = nullptr;
LOG(LogWarning) << "NinePatchComponent has no texture";
return;
}
glm::vec2 texSize;
mVertices = new Renderer::Vertex[6 * 9];
texSize = glm::vec2(static_cast<float>(mTexture->getSize().x()),
static_cast<float>(mTexture->getSize().y()));
// clang-format off
const float imgSizeX[3] = { mCornerSize.x, mSize.x - mCornerSize.x * 2.0f, mCornerSize.x };
const float imgSizeY[3] = { mCornerSize.y, mSize.y - mCornerSize.y * 2.0f, mCornerSize.y };
const float imgPosX[3] = { 0, imgSizeX[0], imgSizeX[0] + imgSizeX[1] };
const float imgPosY[3] = { 0, imgSizeY[0], imgSizeY[0] + imgSizeY[1] };
// The "1 +" in posY and "-" in sizeY is to deal with texture coordinates having a bottom
// left corner origin vs. verticies having a top left origin.
const float texSizeX[3] = { mCornerSize.x / texSize.x, (texSize.x - mCornerSize.x * 2.0f) / texSize.x, mCornerSize.x / texSize.x };
const float texSizeY[3] = { -mCornerSize.y / texSize.y, -(texSize.y - mCornerSize.y * 2.0f) / texSize.y, -mCornerSize.y / texSize.y };
const float texPosX[3] = { 0.0f, texSizeX[0], texSizeX[0] + texSizeX[1] };
const float texPosY[3] = { 1.0f, 1.0f + texSizeY[0], 1.0f + texSizeY[0] + texSizeY[1] };
// clang-format on
int v = 0;
for (int slice = 0; slice < 9; slice++) {
const int sliceX = slice % 3;
const int sliceY = slice / 3;
const glm::vec2 imgPos = glm::vec2(imgPosX[sliceX], imgPosY[sliceY]);
const glm::vec2 imgSize = glm::vec2(imgSizeX[sliceX], imgSizeY[sliceY]);
const glm::vec2 texPos = glm::vec2(texPosX[sliceX], texPosY[sliceY]);
const glm::vec2 texSize = glm::vec2(texSizeX[sliceX], texSizeY[sliceY]);
// clang-format off
mVertices[v + 1] = { { imgPos.x , imgPos.y }, { texPos.x, texPos.y }, 0 };
mVertices[v + 2] = { { imgPos.x , imgPos.y + imgSize.y }, { texPos.x, texPos.y + texSize.y }, 0 };
mVertices[v + 3] = { { imgPos.x + imgSize.x, imgPos.y }, { texPos.x + texSize.x, texPos.y }, 0 };
mVertices[v + 4] = { { imgPos.x + imgSize.x, imgPos.y + imgSize.y }, { texPos.x + texSize.x, texPos.y + texSize.y }, 0 };
// clang-format on
// Round vertices.
for (int i = 1; i < 5; i++)
mVertices[v + i].pos = glm::round(mVertices[v + i].pos);
// Make duplicates of first and last vertex so this can be rendered as a triangle strip.
mVertices[v + 0] = mVertices[v + 1];
mVertices[v + 5] = mVertices[v + 4];
v += 6;
}
updateColors();
}
void NinePatchComponent::render(const glm::mat4& parentTrans)
{
if (!isVisible())
return;
2019-07-22 03:13:48 +00:00
glm::mat4 trans = parentTrans * getTransform();
if (mTexture && mVertices != nullptr) {
Renderer::setMatrix(trans);
2020-09-12 17:17:26 +00:00
if (mOpacity < 255) {
mVertices[0].shaders = Renderer::SHADER_OPACITY;
2020-12-29 11:54:24 +00:00
mVertices[0].opacity = mOpacity / 255.0f;
2020-09-12 17:17:26 +00:00
}
else if (mVertices[0].shaders & Renderer::SHADER_OPACITY) {
// We have reached full opacity, so disable the opacity shader and set
// the vertex opacity to 1.0.
mVertices[0].shaders ^= Renderer::SHADER_OPACITY;
2021-03-23 21:01:47 +00:00
mVertices[0].opacity = 1.0f;
}
mTexture->bind();
2021-03-23 21:01:47 +00:00
Renderer::drawTriangleStrips(&mVertices[0], 6 * 9, trans);
}
renderChildren(trans);
}
void NinePatchComponent::onSizeChanged() { buildVertices(); }
void NinePatchComponent::fitTo(glm::vec2 size, glm::vec3 position, glm::vec2 padding)
{
size += padding;
position[0] -= padding.x / 2.0f;
position[1] -= padding.y / 2.0f;
2013-09-14 16:14:21 +00:00
2021-03-23 21:01:47 +00:00
setSize(size + mCornerSize * 2.0f);
setPosition(position.x + Math::lerp(-mCornerSize.x, mCornerSize.x, mOrigin.x),
position.y + Math::lerp(-mCornerSize.y, mCornerSize.y, mOrigin.y));
}
void NinePatchComponent::setImagePath(const std::string& path)
{
mPath = path;
buildVertices();
}
void NinePatchComponent::setEdgeColor(unsigned int edgeColor)
{
mEdgeColor = edgeColor;
updateColors();
}
void NinePatchComponent::setCenterColor(unsigned int centerColor)
{
mCenterColor = centerColor;
updateColors();
}
void NinePatchComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties)
{
GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "ninepatch");
if (!elem)
return;
if (properties & PATH && elem->has("path"))
setImagePath(elem->get<std::string>("path"));
}