2020-09-21 17:17:34 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-06-06 20:04:05 +00:00
|
|
|
//
|
2020-09-21 17:17:34 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-21 12:25:28 +00:00
|
|
|
// RatingComponent.cpp
|
2020-06-06 20:04:05 +00:00
|
|
|
//
|
2020-06-21 12:25:28 +00:00
|
|
|
// Game rating icons.
|
|
|
|
// Used by gamelist views, metadata editor and scraper.
|
2020-06-06 20:04:05 +00:00
|
|
|
//
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "components/RatingComponent.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
2020-06-09 18:03:31 +00:00
|
|
|
#include "Settings.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "ThemeData.h"
|
2021-07-07 18:31:46 +00:00
|
|
|
#include "resources/TextureResource.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2022-01-19 17:01:54 +00:00
|
|
|
RatingComponent::RatingComponent(bool colorizeChanges)
|
2022-03-14 18:51:48 +00:00
|
|
|
: mRenderer {Renderer::getInstance()}
|
|
|
|
, mColorOriginalValue {DEFAULT_COLORSHIFT}
|
2022-01-16 17:18:28 +00:00
|
|
|
, mColorChangedValue {DEFAULT_COLORSHIFT}
|
|
|
|
, mColorShift {DEFAULT_COLORSHIFT}
|
|
|
|
, mColorShiftEnd {DEFAULT_COLORSHIFT}
|
|
|
|
, mUnfilledColor {DEFAULT_COLORSHIFT}
|
|
|
|
, mColorizeChanges {colorizeChanges}
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 17:35:43 +00:00
|
|
|
mFilledTexture = TextureResource::get(":/graphics/star_filled.svg", true);
|
|
|
|
mUnfilledTexture = TextureResource::get(":/graphics/star_unfilled.svg", true);
|
2020-06-21 12:25:28 +00:00
|
|
|
mValue = 0.5f;
|
2022-01-16 11:09:55 +00:00
|
|
|
mSize = glm::vec2 {64.0f * NUM_RATING_STARS, 64.0f};
|
2020-06-21 12:25:28 +00:00
|
|
|
updateVertices();
|
|
|
|
updateColors();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RatingComponent::setValue(const std::string& value)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (value.empty()) {
|
|
|
|
mValue = 0.0f;
|
|
|
|
}
|
|
|
|
else {
|
2021-07-07 20:08:19 +00:00
|
|
|
// Round to the closest .1 value, i.e. to the closest half-icon.
|
|
|
|
mValue = std::round(stof(value) / 0.1f) / 10.0f;
|
2021-07-07 18:31:46 +00:00
|
|
|
mOriginalValue = static_cast<int>(mValue * 10.0f);
|
2020-07-15 15:44:27 +00:00
|
|
|
|
|
|
|
// If the argument to colorize the rating icons has been passed, set the
|
|
|
|
// color shift accordingly.
|
|
|
|
if (mColorizeChanges) {
|
2021-07-07 18:31:46 +00:00
|
|
|
if (static_cast<int>(mValue * 10.0f) == mOriginalValue)
|
2020-07-15 15:44:27 +00:00
|
|
|
setColorShift(mColorOriginalValue);
|
|
|
|
else
|
|
|
|
setColorShift(mColorChangedValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
// For the special situation where there is a fractional rating in the gamelist.xml
|
|
|
|
// file that has been rounded to a half-star rating, render the rating icons green.
|
|
|
|
// This should only happen if an external scraper has been used or if the file has
|
|
|
|
// been manually edited.
|
|
|
|
if (mColorizeChanges && mValue != stof(value)) {
|
|
|
|
mOriginalValue = ICONCOLOR_USERMARKED;
|
|
|
|
setColorShift(0x449944FF);
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mValue > 1.0f)
|
|
|
|
mValue = 1.0f;
|
|
|
|
else if (mValue < 0.0f)
|
|
|
|
mValue = 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateVertices();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string RatingComponent::getValue() const
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Do not use std::to_string here as it will use the current locale
|
|
|
|
// and that sometimes encodes decimals as commas.
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << mValue;
|
|
|
|
return ss.str();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2022-06-06 20:28:24 +00:00
|
|
|
std::string RatingComponent::getRatingValue(const std::string& rating)
|
2022-01-22 20:25:35 +00:00
|
|
|
{
|
|
|
|
std::stringstream ss;
|
2022-06-06 20:28:24 +00:00
|
|
|
ss << (std::round(stof(rating) / 0.1f) / 10.0f) * NUM_RATING_STARS;
|
2022-01-22 20:25:35 +00:00
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
2022-02-11 21:10:25 +00:00
|
|
|
void RatingComponent::setOpacity(float opacity)
|
2017-06-08 23:18:27 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
mOpacity = opacity;
|
2022-02-12 16:38:55 +00:00
|
|
|
mColorShift =
|
|
|
|
(mColorShift >> 8 << 8) | static_cast<unsigned char>(mOpacity * mThemeOpacity * 255.0f);
|
2020-06-21 12:25:28 +00:00
|
|
|
updateColors();
|
2017-06-08 23:18:27 +00:00
|
|
|
}
|
|
|
|
|
2022-03-21 19:35:24 +00:00
|
|
|
void RatingComponent::setDimming(float dimming)
|
|
|
|
{
|
|
|
|
mDimming = dimming;
|
|
|
|
mVertices[0].dimming = mDimming;
|
|
|
|
mVertices[4].dimming = mDimming;
|
|
|
|
}
|
|
|
|
|
2017-06-08 23:18:27 +00:00
|
|
|
void RatingComponent::setColorShift(unsigned int color)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
mColorShift = color;
|
|
|
|
mColorShiftEnd = color;
|
2020-06-09 18:03:31 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Grab the opacity from the color shift because we may need
|
|
|
|
// to apply it if fading in textures.
|
2022-02-11 21:10:25 +00:00
|
|
|
mOpacity = static_cast<float>(color & 0xff) / 255.0f;
|
2020-06-21 12:25:28 +00:00
|
|
|
updateColors();
|
2017-06-08 23:18:27 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
void RatingComponent::onSizeChanged()
|
|
|
|
{
|
2022-04-10 09:53:44 +00:00
|
|
|
// Make sure the size is not unreasonably large (which may be caused by a mistake in
|
|
|
|
// the theme configuration).
|
|
|
|
mSize.x = glm::clamp(mSize.x, 0.0f, mRenderer->getScreenWidth() / 2.0f);
|
|
|
|
mSize.y = glm::clamp(mSize.y, 0.0f, mRenderer->getScreenHeight() / 2.0f);
|
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
if (mSize.y == 0.0f)
|
|
|
|
mSize.y = mSize.x / NUM_RATING_STARS;
|
|
|
|
else if (mSize.x == 0.0f)
|
|
|
|
mSize.x = mSize.y * NUM_RATING_STARS;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
if (mSize.y > 0.0f) {
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mFilledTexture)
|
2021-10-23 13:53:31 +00:00
|
|
|
mFilledTexture->rasterizeAt(mSize.y, mSize.y);
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mUnfilledTexture)
|
2021-10-23 13:53:31 +00:00
|
|
|
mUnfilledTexture->rasterizeAt(mSize.y, mSize.y);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updateVertices();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RatingComponent::updateVertices()
|
|
|
|
{
|
2022-02-12 12:36:40 +00:00
|
|
|
const float numStars {NUM_RATING_STARS};
|
|
|
|
const float h {getSize().y}; // Ss the same as a single star's width.
|
|
|
|
const float w {getSize().y * mValue * numStars};
|
|
|
|
const float fw {getSize().y * numStars};
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
// clang-format off
|
2022-03-11 22:17:04 +00:00
|
|
|
mVertices[0] = {{0.0f, 0.0f}, {0.0f, 1.0f}, mColorShift};
|
|
|
|
mVertices[1] = {{0.0f, h }, {0.0f, 0.0f}, mColorShift};
|
|
|
|
mVertices[2] = {{w, 0.0f}, {mValue * numStars, 1.0f}, mColorShift};
|
|
|
|
mVertices[3] = {{w, h }, {mValue * numStars, 0.0f}, mColorShift};
|
|
|
|
|
|
|
|
mVertices[4] = {{0.0f, 0.0f}, {0.0f, 1.0f}, mColorShift};
|
|
|
|
mVertices[5] = {{0.0f, h }, {0.0f, 0.0f}, mColorShift};
|
|
|
|
mVertices[6] = {{fw, 0.0f}, {numStars, 1.0f}, mColorShift};
|
|
|
|
mVertices[7] = {{fw, h }, {numStars, 0.0f}, mColorShift};
|
2021-07-07 18:31:46 +00:00
|
|
|
// clang-format on
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2017-06-08 23:18:27 +00:00
|
|
|
void RatingComponent::updateColors()
|
|
|
|
{
|
2021-11-17 16:48:49 +00:00
|
|
|
for (int i = 0; i < 8; ++i)
|
2022-03-11 22:51:41 +00:00
|
|
|
mVertices[i].color = mColorShift;
|
2017-06-08 23:18:27 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 17:30:31 +00:00
|
|
|
void RatingComponent::render(const glm::mat4& parentTrans)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2022-02-12 16:38:55 +00:00
|
|
|
if (!isVisible() || mFilledTexture == nullptr || mUnfilledTexture == nullptr ||
|
|
|
|
mThemeOpacity == 0.0f)
|
2020-06-21 12:25:28 +00:00
|
|
|
return;
|
2020-06-06 20:04:05 +00:00
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::mat4 trans {parentTrans * getTransform()};
|
2020-05-15 16:12:16 +00:00
|
|
|
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->setMatrix(trans);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2022-02-11 21:10:25 +00:00
|
|
|
if (mOpacity > 0.0f) {
|
2020-07-13 18:58:25 +00:00
|
|
|
if (Settings::getInstance()->getBool("DebugImage")) {
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->drawRect(0.0f, 0.0f, mSize.y * NUM_RATING_STARS, mSize.y, 0xFF000033,
|
|
|
|
0xFF000033);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2020-05-15 16:12:16 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mUnfilledTexture->bind()) {
|
|
|
|
if (mUnfilledColor != mColorShift) {
|
2021-11-17 16:48:49 +00:00
|
|
|
for (int i = 0; i < 8; ++i)
|
2022-03-11 22:51:41 +00:00
|
|
|
mVertices[i].color =
|
|
|
|
(mUnfilledColor & 0xFFFFFF00) + (mVertices[i].color & 0x000000FF);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2020-05-15 16:12:16 +00:00
|
|
|
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->drawTriangleStrips(&mVertices[4], 4);
|
|
|
|
mRenderer->bindTexture(0);
|
2020-06-09 18:03:31 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mUnfilledColor != mColorShift)
|
|
|
|
updateColors();
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mFilledTexture->bind()) {
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->drawTriangleStrips(&mVertices[0], 4);
|
|
|
|
mRenderer->bindTexture(0);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-06 20:04:05 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
renderChildren(trans);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool RatingComponent::input(InputConfig* config, Input input)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (config->isMappedTo("a", input) && input.value != 0) {
|
2021-07-07 18:31:46 +00:00
|
|
|
mValue += (1.0f / 2.0f) / NUM_RATING_STARS;
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mValue > 1.05f)
|
|
|
|
mValue = 0.0f;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-07-15 15:44:27 +00:00
|
|
|
// If the argument to colorize the rating icons has been passed,
|
|
|
|
// set the color shift accordingly.
|
|
|
|
if (mColorizeChanges) {
|
2021-07-07 18:31:46 +00:00
|
|
|
if (static_cast<int>(mValue * 10.0f) == mOriginalValue)
|
2020-07-15 15:44:27 +00:00
|
|
|
setColorShift(mColorOriginalValue);
|
|
|
|
else
|
|
|
|
setColorShift(mColorChangedValue);
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
updateVertices();
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return GuiComponent::input(config, input);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 20:04:05 +00:00
|
|
|
void RatingComponent::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-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
using namespace ThemeFlags;
|
|
|
|
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "rating");
|
|
|
|
if (!elem)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool imgChanged = false;
|
|
|
|
if (properties & PATH && elem->has("filledPath")) {
|
|
|
|
mFilledTexture = TextureResource::get(elem->get<std::string>("filledPath"), true);
|
|
|
|
imgChanged = true;
|
|
|
|
}
|
|
|
|
if (properties & PATH && elem->has("unfilledPath")) {
|
|
|
|
mUnfilledTexture = TextureResource::get(elem->get<std::string>("unfilledPath"), true);
|
|
|
|
imgChanged = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (properties & COLOR) {
|
|
|
|
if (elem->has("color"))
|
|
|
|
setColorShift(elem->get<unsigned int>("color"));
|
|
|
|
|
|
|
|
if (elem->has("unfilledColor"))
|
|
|
|
mUnfilledColor = elem->get<unsigned int>("unfilledColor");
|
|
|
|
else
|
|
|
|
mUnfilledColor = mColorShift;
|
|
|
|
}
|
|
|
|
|
2021-03-13 10:46:19 +00:00
|
|
|
GuiComponent::applyTheme(theme, view, element, properties);
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (imgChanged)
|
|
|
|
onSizeChanged();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<HelpPrompt> RatingComponent::getHelpPrompts()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::vector<HelpPrompt> prompts;
|
|
|
|
prompts.push_back(HelpPrompt("a", "add half star"));
|
|
|
|
return prompts;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|