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-09-03 19:43:36 +00:00
|
|
|
RatingComponent::RatingComponent(bool colorizeChanges, bool linearInterpolation)
|
2022-03-14 18:51:48 +00:00
|
|
|
: mRenderer {Renderer::getInstance()}
|
2022-09-01 15:40:29 +00:00
|
|
|
, mValue {0.5f}
|
|
|
|
, mImageRatio {1.0f}
|
2023-05-07 20:56:24 +00:00
|
|
|
, mColorOriginalValue {mMenuColorPrimary}
|
|
|
|
, mColorChangedValue {mMenuColorPrimary}
|
2022-01-16 17:18:28 +00:00
|
|
|
, mColorizeChanges {colorizeChanges}
|
2022-09-01 15:40:29 +00:00
|
|
|
, mOverlay {true}
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2022-08-31 20:30:31 +00:00
|
|
|
mSize = glm::vec2 {std::round(mRenderer->getScreenHeight() * 0.06f) * NUM_RATING_STARS,
|
|
|
|
std::round(mRenderer->getScreenHeight() * 0.06f)};
|
2022-08-28 18:11:20 +00:00
|
|
|
|
|
|
|
mIconFilled.setResize(mSize, false);
|
|
|
|
mIconFilled.setTileSize(mSize.y, mSize.y);
|
2022-09-08 16:59:33 +00:00
|
|
|
mIconFilled.setDynamic(false);
|
2022-09-03 19:43:36 +00:00
|
|
|
mIconFilled.setLinearInterpolation(linearInterpolation);
|
2023-05-07 20:56:24 +00:00
|
|
|
mIconFilled.setColorShift(mMenuColorPrimary);
|
2022-08-28 18:11:20 +00:00
|
|
|
|
|
|
|
mIconUnfilled.setResize(mSize, false);
|
|
|
|
mIconUnfilled.setTileSize(mSize.y, mSize.y);
|
2022-09-08 16:59:33 +00:00
|
|
|
mIconUnfilled.setDynamic(false);
|
2022-09-03 19:43:36 +00:00
|
|
|
mIconUnfilled.setLinearInterpolation(linearInterpolation);
|
2023-05-07 20:56:24 +00:00
|
|
|
mIconUnfilled.setColorShift(mMenuColorPrimary);
|
2022-08-28 18:11:20 +00:00
|
|
|
|
|
|
|
mIconFilled.setImage(std::string(":/graphics/star_filled.svg"), true);
|
|
|
|
mIconUnfilled.setImage(std::string(":/graphics/star_unfilled.svg"), true);
|
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)
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.setColorShift(mColorOriginalValue);
|
2020-07-15 15:44:27 +00:00
|
|
|
else
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.setColorShift(mColorChangedValue);
|
2020-07-15 15:44:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)) {
|
2023-05-07 20:56:24 +00:00
|
|
|
mOriginalValue = mMenuColorBlue;
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.setColorShift(0x449944FF);
|
2020-07-15 15:44:27 +00:00
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mValue > 1.0f)
|
|
|
|
mValue = 1.0f;
|
|
|
|
else if (mValue < 0.0f)
|
|
|
|
mValue = 0.0f;
|
|
|
|
}
|
2022-08-31 19:19:09 +00:00
|
|
|
|
2022-09-01 15:40:29 +00:00
|
|
|
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});
|
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-03-21 19:35:24 +00:00
|
|
|
void RatingComponent::setDimming(float dimming)
|
|
|
|
{
|
|
|
|
mDimming = dimming;
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.setDimming(dimming);
|
|
|
|
mIconUnfilled.setDimming(dimming);
|
2017-06-08 23:18:27 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
void RatingComponent::onSizeChanged()
|
|
|
|
{
|
2022-08-28 18:11:20 +00:00
|
|
|
mSize = glm::round(mSize);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2022-08-28 18:11:20 +00:00
|
|
|
if (mSize.x == 0.0f)
|
|
|
|
mSize.x = mSize.y * NUM_RATING_STARS;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.getTexture()->setSize(mSize.y, mSize.y);
|
2022-09-07 17:29:56 +00:00
|
|
|
mIconFilled.setTileSize(mSize.y, mSize.y);
|
2022-09-01 15:40:29 +00:00
|
|
|
mIconFilled.setResize(glm::vec2 {std::round(mSize.y * mImageRatio) * NUM_RATING_STARS, mSize.y},
|
|
|
|
true);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconUnfilled.getTexture()->setSize(mSize.y, mSize.y);
|
2022-09-07 17:29:56 +00:00
|
|
|
mIconUnfilled.setTileSize(mSize.y, mSize.y);
|
2022-09-01 15:40:29 +00:00
|
|
|
mIconUnfilled.setResize(
|
|
|
|
glm::vec2 {std::round(mSize.y * mImageRatio) * NUM_RATING_STARS, mSize.y}, true);
|
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-08-28 18:11:20 +00:00
|
|
|
if (!isVisible() || mThemeOpacity == 0.0f || mOpacity == 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()};
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconUnfilled.setOpacity(mOpacity * mThemeOpacity);
|
|
|
|
mIconFilled.setOpacity(mOpacity * mThemeOpacity);
|
2022-08-31 19:19:09 +00:00
|
|
|
|
|
|
|
mIconUnfilled.render(trans);
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.render(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)
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.setColorShift(mColorOriginalValue);
|
2020-07-15 15:44:27 +00:00
|
|
|
else
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.setColorShift(mColorChangedValue);
|
2020-07-15 15:44:27 +00:00
|
|
|
}
|
2022-08-31 19:19:09 +00:00
|
|
|
|
2022-09-01 15:40:29 +00:00
|
|
|
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});
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
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;
|
2022-08-23 20:24:24 +00:00
|
|
|
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "rating")};
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (!elem)
|
|
|
|
return;
|
|
|
|
|
2022-08-28 18:11:20 +00:00
|
|
|
GuiComponent::applyTheme(theme, view, element, properties ^ ThemeFlags::SIZE);
|
2022-08-23 20:24:24 +00:00
|
|
|
|
2022-08-28 18:11:20 +00:00
|
|
|
glm::vec2 scale {getParent() ?
|
|
|
|
getParent()->getSize() :
|
|
|
|
glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())};
|
2022-08-23 20:24:24 +00:00
|
|
|
|
2022-09-01 15:40:29 +00:00
|
|
|
{
|
|
|
|
// Read the image file in order to retrieve the image dimensions needed to calculate
|
|
|
|
// the aspect ratio constant.
|
|
|
|
if (properties & PATH && elem->has("filledPath")) {
|
2022-11-03 15:03:21 +00:00
|
|
|
const std::string& path {std::string(elem->get<std::string>("filledPath"))};
|
2022-09-01 15:40:29 +00:00
|
|
|
if (Utils::FileSystem::isRegularFile(path) || Utils::FileSystem::isSymlink(path)) {
|
|
|
|
auto tempImage =
|
2022-09-05 21:36:49 +00:00
|
|
|
TextureResource::get(path, false, false, false, false, false, 0, 0, 0.0f, 0.0f);
|
2022-09-01 15:40:29 +00:00
|
|
|
mImageRatio = static_cast<float>(tempImage->getSize().x) /
|
|
|
|
static_cast<float>(tempImage->getSize().y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-28 18:11:20 +00:00
|
|
|
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";
|
2022-08-31 20:30:31 +00:00
|
|
|
ratingSize.y = 0.06f;
|
2022-08-28 18:11:20 +00:00
|
|
|
}
|
|
|
|
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);
|
2022-08-31 19:19:09 +00:00
|
|
|
mSize = glm::round(ratingSize * scale);
|
2022-08-28 18:11:20 +00:00
|
|
|
if (mSize.y == 0.0f)
|
2022-09-01 15:40:29 +00:00
|
|
|
mSize.y = std::round(mSize.x / mImageRatio) / NUM_RATING_STARS;
|
2022-08-28 18:11:20 +00:00
|
|
|
else
|
2022-09-01 15:40:29 +00:00
|
|
|
mSize.x = std::round(mSize.y * mImageRatio) * NUM_RATING_STARS;
|
2022-08-28 18:11:20 +00:00
|
|
|
}
|
|
|
|
|
2022-08-28 18:45:04 +00:00
|
|
|
bool linearInterpolation {false};
|
|
|
|
|
|
|
|
if (elem->has("interpolation")) {
|
2022-11-03 15:03:21 +00:00
|
|
|
const std::string& interpolation {elem->get<std::string>("interpolation")};
|
2022-08-28 18:45:04 +00:00
|
|
|
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 << "\"";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-07 17:29:56 +00:00
|
|
|
mIconFilled.setTileSize(std::round(mSize.y * mImageRatio), mSize.y);
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.setResize(glm::vec2 {mSize}, false);
|
2022-08-23 20:50:14 +00:00
|
|
|
|
2022-09-01 15:40:29 +00:00
|
|
|
if (properties & PATH && elem->has("filledPath") &&
|
|
|
|
(Utils::FileSystem::isRegularFile(elem->get<std::string>("filledPath")) ||
|
|
|
|
Utils::FileSystem::isSymlink(elem->get<std::string>("filledPath")))) {
|
2022-08-28 18:45:04 +00:00
|
|
|
mIconFilled.setLinearInterpolation(linearInterpolation);
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconFilled.setImage(std::string(elem->get<std::string>("filledPath")), true);
|
2022-09-01 15:40:29 +00:00
|
|
|
mIconFilled.getTexture()->setSize(std::round(mSize.y * mImageRatio), mSize.y);
|
|
|
|
mIconFilled.onSizeChanged();
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2022-08-28 18:11:20 +00:00
|
|
|
else {
|
|
|
|
mIconFilled.setImage(std::string(":/graphics/star_filled.svg"), true);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
2022-09-07 17:29:56 +00:00
|
|
|
mIconUnfilled.setTileSize(std::round(mSize.y * mImageRatio), mSize.y);
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconUnfilled.setResize(glm::vec2 {mSize}, false);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2022-09-01 15:40:29 +00:00
|
|
|
if (properties & PATH && elem->has("unfilledPath") &&
|
|
|
|
(Utils::FileSystem::isRegularFile(elem->get<std::string>("unfilledPath")) ||
|
|
|
|
Utils::FileSystem::isSymlink(elem->get<std::string>("unfilledPath")))) {
|
2022-08-28 18:45:04 +00:00
|
|
|
mIconUnfilled.setLinearInterpolation(linearInterpolation);
|
2022-08-28 18:11:20 +00:00
|
|
|
mIconUnfilled.setImage(std::string(elem->get<std::string>("unfilledPath")), true);
|
2022-09-01 15:40:29 +00:00
|
|
|
mIconUnfilled.getTexture()->setSize(std::round(mSize.y * mImageRatio), mSize.y);
|
|
|
|
mIconUnfilled.onSizeChanged();
|
2022-08-28 18:11:20 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
mIconUnfilled.setImage(std::string(":/graphics/star_unfilled.svg"), true);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
2022-09-01 15:40:29 +00:00
|
|
|
if (elem->has("overlay") && !elem->get<bool>("overlay"))
|
|
|
|
mOverlay = false;
|
|
|
|
|
2022-08-28 18:11:20 +00:00
|
|
|
if (properties & COLOR) {
|
|
|
|
if (elem->has("color")) {
|
|
|
|
mIconFilled.setColorShift(elem->get<unsigned int>("color"));
|
|
|
|
mIconUnfilled.setColorShift(elem->get<unsigned int>("color"));
|
|
|
|
}
|
2023-05-07 20:56:24 +00:00
|
|
|
else {
|
|
|
|
mIconFilled.setColorShift(0xFFFFFFFF);
|
2023-06-29 19:04:44 +00:00
|
|
|
mIconUnfilled.setColorShift(0xFFFFFFFF);
|
2023-05-07 20:56:24 +00:00
|
|
|
}
|
2022-08-28 18:11:20 +00:00
|
|
|
}
|
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
|
|
|
}
|