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

285 lines
10 KiB
C++

// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// RatingComponent.cpp
//
// Game rating icons.
// Used by gamelist views, metadata editor and scraper.
//
#include "components/RatingComponent.h"
#include "Settings.h"
#include "ThemeData.h"
#include "resources/TextureResource.h"
RatingComponent::RatingComponent(bool colorizeChanges, bool linearInterpolation)
: mRenderer {Renderer::getInstance()}
, mValue {0.5f}
, mImageRatio {1.0f}
, mColorOriginalValue {mMenuColorPrimary}
, mColorChangedValue {mMenuColorPrimary}
, mColorizeChanges {colorizeChanges}
, mOverlay {true}
{
mSize = glm::vec2 {std::round(mRenderer->getScreenHeight() * 0.06f) * NUM_RATING_STARS,
std::round(mRenderer->getScreenHeight() * 0.06f)};
mIconFilled.setResize(mSize, false);
mIconFilled.setTileSize(mSize.y, mSize.y);
mIconFilled.setDynamic(false);
mIconFilled.setLinearInterpolation(linearInterpolation);
mIconFilled.setColorShift(mMenuColorPrimary);
mIconUnfilled.setResize(mSize, false);
mIconUnfilled.setTileSize(mSize.y, mSize.y);
mIconUnfilled.setDynamic(false);
mIconUnfilled.setLinearInterpolation(linearInterpolation);
mIconUnfilled.setColorShift(mMenuColorPrimary);
mIconFilled.setImage(std::string(":/graphics/star_filled.svg"), true);
mIconUnfilled.setImage(std::string(":/graphics/star_unfilled.svg"), true);
}
void RatingComponent::setValue(const std::string& value)
{
if (value.empty()) {
mValue = 0.0f;
}
else {
// Round to the closest .1 value, i.e. to the closest half-icon.
mValue = std::round(stof(value) / 0.1f) / 10.0f;
mOriginalValue = static_cast<int>(mValue * 10.0f);
// If the argument to colorize the rating icons has been passed, set the
// color shift accordingly.
if (mColorizeChanges) {
if (static_cast<int>(mValue * 10.0f) == mOriginalValue)
mIconFilled.setColorShift(mColorOriginalValue);
else
mIconFilled.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 = mMenuColorBlue;
mIconFilled.setColorShift(0x449944FF);
}
if (mValue > 1.0f)
mValue = 1.0f;
else if (mValue < 0.0f)
mValue = 0.0f;
}
const float clipValue {std::round(mIconUnfilled.getSize().x * mValue)};
if (!mOverlay)
mIconUnfilled.setClipRegion(glm::vec4 {clipValue, 0.0f, mSize.x, mSize.y});
mIconFilled.setClipRegion(glm::vec4 {0.0f, 0.0f, clipValue, mSize.y});
}
std::string RatingComponent::getValue() const
{
// 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();
}
std::string RatingComponent::getRatingValue(const std::string& rating)
{
std::stringstream ss;
ss << (std::round(stof(rating) / 0.1f) / 10.0f) * NUM_RATING_STARS;
return ss.str();
}
void RatingComponent::setDimming(float dimming)
{
mDimming = dimming;
mIconFilled.setDimming(dimming);
mIconUnfilled.setDimming(dimming);
}
void RatingComponent::onSizeChanged()
{
mSize = glm::round(mSize);
if (mSize.x == 0.0f)
mSize.x = mSize.y * NUM_RATING_STARS;
mIconFilled.getTexture()->setSize(mSize.y, mSize.y);
mIconFilled.setTileSize(mSize.y, mSize.y);
mIconFilled.setResize(glm::vec2 {std::round(mSize.y * mImageRatio) * NUM_RATING_STARS, mSize.y},
true);
mIconUnfilled.getTexture()->setSize(mSize.y, mSize.y);
mIconUnfilled.setTileSize(mSize.y, mSize.y);
mIconUnfilled.setResize(
glm::vec2 {std::round(mSize.y * mImageRatio) * NUM_RATING_STARS, mSize.y}, true);
}
void RatingComponent::render(const glm::mat4& parentTrans)
{
if (!isVisible() || mThemeOpacity == 0.0f || mOpacity == 0.0f)
return;
glm::mat4 trans {parentTrans * getTransform()};
mIconUnfilled.setOpacity(mOpacity * mThemeOpacity);
mIconFilled.setOpacity(mOpacity * mThemeOpacity);
mIconUnfilled.render(trans);
mIconFilled.render(trans);
}
bool RatingComponent::input(InputConfig* config, Input input)
{
if (config->isMappedTo("a", input) && input.value != 0) {
mValue += (1.0f / 2.0f) / NUM_RATING_STARS;
if (mValue > 1.05f)
mValue = 0.0f;
// If the argument to colorize the rating icons has been passed,
// set the color shift accordingly.
if (mColorizeChanges) {
if (static_cast<int>(mValue * 10.0f) == mOriginalValue)
mIconFilled.setColorShift(mColorOriginalValue);
else
mIconFilled.setColorShift(mColorChangedValue);
}
const float clipValue {std::round(mIconUnfilled.getSize().x * mValue)};
if (!mOverlay)
mIconUnfilled.setClipRegion(glm::vec4 {clipValue, 0.0f, mSize.x, mSize.y});
mIconFilled.setClipRegion(glm::vec4 {0.0f, 0.0f, clipValue, mSize.y});
}
return GuiComponent::input(config, input);
}
void RatingComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties)
{
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "rating")};
if (!elem)
return;
GuiComponent::applyTheme(theme, view, element, properties ^ ThemeFlags::SIZE);
glm::vec2 scale {getParent() ?
getParent()->getSize() :
glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())};
{
// Read the image file in order to retrieve the image dimensions needed to calculate
// the aspect ratio constant.
if (properties & PATH && elem->has("filledPath")) {
const std::string& path {std::string(elem->get<std::string>("filledPath"))};
if (Utils::FileSystem::isRegularFile(path) || Utils::FileSystem::isSymlink(path)) {
auto tempImage =
TextureResource::get(path, false, false, false, false, false, 0, 0, 0.0f, 0.0f);
mImageRatio = static_cast<float>(tempImage->getSize().x) /
static_cast<float>(tempImage->getSize().y);
}
}
}
if (elem->has("size")) {
glm::vec2 ratingSize {elem->get<glm::vec2>("size")};
if (ratingSize == glm::vec2 {0.0f, 0.0f}) {
LOG(LogWarning) << "RatingComponent: Invalid theme configuration, property <size> "
"for element \""
<< element.substr(7) << "\" is set to zero";
ratingSize.y = 0.06f;
}
if (ratingSize.x > 0.0f)
ratingSize.x = glm::clamp(ratingSize.x, 0.01f, 1.0f);
if (ratingSize.y > 0.0f)
ratingSize.y = glm::clamp(ratingSize.y, 0.01f, 0.5f);
mSize = glm::round(ratingSize * scale);
if (mSize.y == 0.0f)
mSize.y = std::round(mSize.x / mImageRatio) / NUM_RATING_STARS;
else
mSize.x = std::round(mSize.y * mImageRatio) * NUM_RATING_STARS;
}
bool linearInterpolation {false};
if (elem->has("interpolation")) {
const std::string& interpolation {elem->get<std::string>("interpolation")};
if (interpolation == "linear") {
linearInterpolation = true;
}
else if (interpolation == "nearest") {
linearInterpolation = false;
}
else {
linearInterpolation = false;
LOG(LogWarning)
<< "RatingComponent: Invalid theme configuration, property <interpolation> "
"for element \""
<< element.substr(7) << "\" defined as \"" << interpolation << "\"";
}
}
mIconFilled.setTileSize(std::round(mSize.y * mImageRatio), mSize.y);
mIconFilled.setResize(glm::vec2 {mSize}, false);
if (properties & PATH && elem->has("filledPath") &&
(Utils::FileSystem::isRegularFile(elem->get<std::string>("filledPath")) ||
Utils::FileSystem::isSymlink(elem->get<std::string>("filledPath")))) {
mIconFilled.setLinearInterpolation(linearInterpolation);
mIconFilled.setImage(std::string(elem->get<std::string>("filledPath")), true);
mIconFilled.getTexture()->setSize(std::round(mSize.y * mImageRatio), mSize.y);
mIconFilled.onSizeChanged();
}
else {
mIconFilled.setImage(std::string(":/graphics/star_filled.svg"), true);
}
mIconUnfilled.setTileSize(std::round(mSize.y * mImageRatio), mSize.y);
mIconUnfilled.setResize(glm::vec2 {mSize}, false);
if (properties & PATH && elem->has("unfilledPath") &&
(Utils::FileSystem::isRegularFile(elem->get<std::string>("unfilledPath")) ||
Utils::FileSystem::isSymlink(elem->get<std::string>("unfilledPath")))) {
mIconUnfilled.setLinearInterpolation(linearInterpolation);
mIconUnfilled.setImage(std::string(elem->get<std::string>("unfilledPath")), true);
mIconUnfilled.getTexture()->setSize(std::round(mSize.y * mImageRatio), mSize.y);
mIconUnfilled.onSizeChanged();
}
else {
mIconUnfilled.setImage(std::string(":/graphics/star_unfilled.svg"), true);
}
if (elem->has("overlay") && !elem->get<bool>("overlay"))
mOverlay = false;
if (properties & COLOR) {
if (elem->has("color")) {
mIconFilled.setColorShift(elem->get<unsigned int>("color"));
mIconUnfilled.setColorShift(elem->get<unsigned int>("color"));
}
else {
mIconFilled.setColorShift(0xFFFFFFFF);
mIconUnfilled.setColorShift(0xFFFFFFFF);
}
}
}
std::vector<HelpPrompt> RatingComponent::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("a", "add half star"));
return prompts;
}