2020-09-21 17:17:34 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-06-28 16:39:18 +00:00
|
|
|
//
|
2020-09-21 17:17:34 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-28 16:39:18 +00:00
|
|
|
// NinePatchComponent.cpp
|
|
|
|
//
|
|
|
|
// Breaks up an image into 3x3 patches to accomodate resizing without distortions.
|
|
|
|
//
|
|
|
|
|
2014-06-20 01:30:09 +00:00
|
|
|
#include "components/NinePatchComponent.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
2014-06-20 01:30:09 +00:00
|
|
|
#include "Log.h"
|
|
|
|
#include "ThemeData.h"
|
2021-11-11 18:47:59 +00:00
|
|
|
#include "resources/Font.h"
|
2021-07-07 18:31:46 +00:00
|
|
|
#include "resources/TextureResource.h"
|
2013-08-23 02:41:40 +00:00
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
NinePatchComponent::NinePatchComponent(Window* window,
|
|
|
|
const std::string& path,
|
|
|
|
unsigned int edgeColor,
|
|
|
|
unsigned int centerColor)
|
|
|
|
: GuiComponent(window)
|
2021-09-18 07:53:26 +00:00
|
|
|
, mVertices(nullptr)
|
|
|
|
, mPath(path)
|
2021-07-07 18:31:46 +00:00
|
|
|
, mCornerSize(16.0f, 16.0f)
|
|
|
|
, mEdgeColor(edgeColor)
|
|
|
|
, mCenterColor(centerColor)
|
2013-08-23 02:41:40 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
if (!mPath.empty())
|
|
|
|
buildVertices();
|
2013-08-23 02:41:40 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 22:23:36 +00:00
|
|
|
NinePatchComponent::~NinePatchComponent()
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
if (mVertices != nullptr)
|
|
|
|
delete[] mVertices;
|
2015-02-09 22:23:36 +00:00
|
|
|
}
|
|
|
|
|
2013-09-14 15:58:34 +00:00
|
|
|
void NinePatchComponent::updateColors()
|
|
|
|
{
|
2020-12-18 15:49:11 +00:00
|
|
|
const unsigned int edgeColor = Renderer::convertRGBAToABGR(mEdgeColor);
|
|
|
|
const unsigned int centerColor = Renderer::convertRGBAToABGR(mCenterColor);
|
2019-08-08 20:16:11 +00:00
|
|
|
|
2021-01-13 22:46:51 +00:00
|
|
|
for (int i = 0; i < 6 * 9; i++)
|
2020-06-28 16:39:18 +00:00
|
|
|
mVertices[i].col = edgeColor;
|
2019-08-08 20:16:11 +00:00
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
for (int i = 6 * 4; i < 6; i++)
|
|
|
|
mVertices[(6 * 4) + i].col = centerColor;
|
2013-09-14 15:58:34 +00:00
|
|
|
}
|
|
|
|
|
2013-08-23 02:41:40 +00:00
|
|
|
void NinePatchComponent::buildVertices()
|
|
|
|
{
|
2021-10-29 17:38:45 +00:00
|
|
|
if (mSize.x == 0.0f || mSize.y == 0.0f)
|
|
|
|
return;
|
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (mVertices != nullptr)
|
|
|
|
delete[] mVertices;
|
|
|
|
|
2021-10-30 11:07:07 +00:00
|
|
|
glm::vec2 relCornerSize{};
|
|
|
|
|
|
|
|
// Don't scale the rasterized version of the frame as it would look bad.
|
|
|
|
if (mPath.substr(mPath.size() - 4, std::string::npos) == ".png") {
|
|
|
|
relCornerSize = mCornerSize;
|
|
|
|
}
|
|
|
|
else {
|
2021-11-11 18:47:59 +00:00
|
|
|
// Scale the corner size relative to the screen resolution (using the medium sized
|
|
|
|
// default font as size reference).
|
2021-10-30 11:07:07 +00:00
|
|
|
relCornerSize = glm::round(
|
2021-11-11 18:47:59 +00:00
|
|
|
mCornerSize * (Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.0568f / 2.0f));
|
2021-10-30 11:07:07 +00:00
|
|
|
}
|
2021-07-02 15:36:05 +00:00
|
|
|
|
2021-10-29 17:38:45 +00:00
|
|
|
mTexture = TextureResource::get(mPath, false, false, false);
|
|
|
|
glm::vec2 texSize = relCornerSize * 3.0f;
|
|
|
|
|
|
|
|
mTexture->rasterizeAt(texSize.x, texSize.y);
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2021-08-17 16:41:45 +00:00
|
|
|
if (mTexture->getSize() == glm::ivec2{}) {
|
2020-06-28 16:39:18 +00:00
|
|
|
mVertices = nullptr;
|
2021-01-13 22:46:51 +00:00
|
|
|
LOG(LogWarning) << "NinePatchComponent has no texture";
|
2020-06-28 16:39:18 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mVertices = new Renderer::Vertex[6 * 9];
|
|
|
|
|
2021-10-29 17:38:45 +00:00
|
|
|
const float imgSizeX[3]{relCornerSize.x, mSize.x - relCornerSize.x * 2.0f, relCornerSize.x};
|
|
|
|
const float imgSizeY[3]{relCornerSize.y, mSize.y - relCornerSize.y * 2.0f, relCornerSize.y};
|
2021-08-17 16:41:45 +00:00
|
|
|
const float imgPosX[3]{0, imgSizeX[0], imgSizeX[0] + imgSizeX[1]};
|
|
|
|
const float imgPosY[3]{0, imgSizeY[0], imgSizeY[0] + imgSizeY[1]};
|
2020-06-28 16:39:18 +00:00
|
|
|
|
|
|
|
// 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.
|
2021-08-17 18:55:29 +00:00
|
|
|
// clang-format off
|
2021-10-29 17:38:45 +00:00
|
|
|
const float texSizeX[3]{relCornerSize.x / texSize.x, (texSize.x - relCornerSize.x * 2.0f) / texSize.x, relCornerSize.x / texSize.x};
|
|
|
|
const float texSizeY[3]{-relCornerSize.y / texSize.y, -(texSize.y - relCornerSize.y * 2.0f) / texSize.y, -relCornerSize.y / texSize.y};
|
2021-08-17 16:41:45 +00:00
|
|
|
|
|
|
|
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]};
|
2021-07-07 18:31:46 +00:00
|
|
|
// clang-format on
|
2020-06-28 16:39:18 +00:00
|
|
|
|
|
|
|
int v = 0;
|
|
|
|
|
|
|
|
for (int slice = 0; slice < 9; slice++) {
|
2021-08-17 16:41:45 +00:00
|
|
|
const int sliceX{slice % 3};
|
|
|
|
const int sliceY{slice / 3};
|
|
|
|
const glm::vec2 imgPos{imgPosX[sliceX], imgPosY[sliceY]};
|
|
|
|
const glm::vec2 imgSize{imgSizeX[sliceX], imgSizeY[sliceY]};
|
|
|
|
const glm::vec2 texPos{texPosX[sliceX], texPosY[sliceY]};
|
2021-09-19 17:46:59 +00:00
|
|
|
const glm::vec2 texSizeSlice{texSizeX[sliceX], texSizeY[sliceY]};
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
// clang-format off
|
2021-09-19 17:46:59 +00:00
|
|
|
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 + texSizeSlice.y}, 0};
|
|
|
|
mVertices[v + 3] = {{imgPos.x + imgSize.x, imgPos.y }, {texPos.x + texSizeSlice.x, texPos.y }, 0};
|
|
|
|
mVertices[v + 4] = {{imgPos.x + imgSize.x, imgPos.y + imgSize.y}, {texPos.x + texSizeSlice.x, texPos.y + texSizeSlice.y}, 0};
|
2021-07-07 18:31:46 +00:00
|
|
|
// clang-format on
|
2020-06-28 16:39:18 +00:00
|
|
|
|
|
|
|
// Round vertices.
|
2021-01-13 22:46:51 +00:00
|
|
|
for (int i = 1; i < 5; i++)
|
2021-08-16 16:25:01 +00:00
|
|
|
mVertices[v + i].pos = glm::round(mVertices[v + i].pos);
|
2020-06-28 16:39:18 +00:00
|
|
|
|
|
|
|
// 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();
|
2013-08-23 02:41:40 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 17:30:31 +00:00
|
|
|
void NinePatchComponent::render(const glm::mat4& parentTrans)
|
2013-08-23 02:41:40 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
if (!isVisible())
|
|
|
|
return;
|
2019-07-22 03:13:48 +00:00
|
|
|
|
2021-08-17 16:41:45 +00:00
|
|
|
glm::mat4 trans{parentTrans * getTransform()};
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
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
|
|
|
}
|
2020-09-27 12:41:59 +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;
|
2020-09-27 12:41:59 +00:00
|
|
|
}
|
2020-06-28 16:39:18 +00:00
|
|
|
mTexture->bind();
|
2021-03-23 21:01:47 +00:00
|
|
|
Renderer::drawTriangleStrips(&mVertices[0], 6 * 9, trans);
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
2013-08-23 02:41:40 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
renderChildren(trans);
|
2013-08-23 02:41:40 +00:00
|
|
|
}
|
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
void NinePatchComponent::fitTo(glm::vec2 size, glm::vec3 position, glm::vec2 padding)
|
2013-08-23 02:41:40 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
size += padding;
|
2021-08-16 16:25:01 +00:00
|
|
|
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);
|
2021-08-17 18:55:29 +00:00
|
|
|
setPosition(position.x + glm::mix(-mCornerSize.x, mCornerSize.x, mOrigin.x),
|
|
|
|
position.y + glm::mix(-mCornerSize.y, mCornerSize.y, mOrigin.y));
|
2013-08-23 02:41:40 +00:00
|
|
|
}
|
2013-09-14 15:58:34 +00:00
|
|
|
|
|
|
|
void NinePatchComponent::setImagePath(const std::string& path)
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mPath = path;
|
|
|
|
buildVertices();
|
2013-09-14 15:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void NinePatchComponent::setEdgeColor(unsigned int edgeColor)
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mEdgeColor = edgeColor;
|
|
|
|
updateColors();
|
2013-09-14 15:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void NinePatchComponent::setCenterColor(unsigned int centerColor)
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mCenterColor = centerColor;
|
|
|
|
updateColors();
|
2013-09-14 15:58:34 +00:00
|
|
|
}
|
2014-01-01 05:39:22 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
void NinePatchComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
2021-07-07 18:31:46 +00:00
|
|
|
const std::string& view,
|
|
|
|
const std::string& element,
|
|
|
|
unsigned int properties)
|
2014-01-01 05:39:22 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
GuiComponent::applyTheme(theme, view, element, properties);
|
2014-01-01 05:39:22 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
using namespace ThemeFlags;
|
|
|
|
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "ninepatch");
|
2014-01-01 05:39:22 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (!elem)
|
|
|
|
return;
|
2014-01-01 05:39:22 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (properties & PATH && elem->has("path"))
|
|
|
|
setImagePath(elem->get<std::string>("path"));
|
2014-01-01 05:39:22 +00:00
|
|
|
}
|