2022-02-04 20:42:08 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
//
|
|
|
|
|
// EmulationStation Desktop Edition
|
|
|
|
|
// CarouselComponent.cpp
|
|
|
|
|
//
|
|
|
|
|
// Carousel.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include "components/CarouselComponent.h"
|
|
|
|
|
|
2022-02-06 13:01:40 +00:00
|
|
|
#include "Log.h"
|
|
|
|
|
#include "animations/LambdaAnimation.h"
|
|
|
|
|
|
2022-02-04 20:42:08 +00:00
|
|
|
CarouselComponent::CarouselComponent()
|
2022-02-06 13:01:40 +00:00
|
|
|
: IList<CarouselElement, SystemData*> {LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP}
|
2022-03-14 18:51:48 +00:00
|
|
|
, mRenderer {Renderer::getInstance()}
|
2022-02-06 13:01:40 +00:00
|
|
|
, mCamOffset {0.0f}
|
|
|
|
|
, mPreviousScrollVelocity {0}
|
|
|
|
|
, mType {HORIZONTAL}
|
2022-02-07 20:05:56 +00:00
|
|
|
, mFont {Font::get(FONT_SIZE_LARGE)}
|
|
|
|
|
, mTextColor {0x000000FF}
|
|
|
|
|
, mTextBackgroundColor {0xFFFFFF00}
|
|
|
|
|
, mLineSpacing {1.5f}
|
2022-02-10 23:19:08 +00:00
|
|
|
, mLogoHorizontalAlignment {ALIGN_CENTER}
|
|
|
|
|
, mLogoVerticalAlignment {ALIGN_CENTER}
|
2022-03-16 23:02:16 +00:00
|
|
|
, mMaxLogoCount {3.0f}
|
2022-02-06 13:01:40 +00:00
|
|
|
, mLogoSize {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f}
|
|
|
|
|
, mLogoScale {1.2f}
|
|
|
|
|
, mLogoRotation {7.5f}
|
2022-02-07 20:05:56 +00:00
|
|
|
, mLogoRotationOrigin {-3.0f, 0.5f}
|
2022-02-06 13:01:40 +00:00
|
|
|
, mCarouselColor {0}
|
|
|
|
|
, mCarouselColorEnd {0}
|
|
|
|
|
, mColorGradientHorizontal {true}
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-07 20:05:56 +00:00
|
|
|
void CarouselComponent::addEntry(const std::shared_ptr<ThemeData>& theme,
|
|
|
|
|
Entry& entry,
|
|
|
|
|
bool legacyMode)
|
2022-02-06 13:01:40 +00:00
|
|
|
{
|
|
|
|
|
// Make logo.
|
2022-02-07 20:05:56 +00:00
|
|
|
if (legacyMode) {
|
|
|
|
|
const ThemeData::ThemeElement* logoElem {
|
|
|
|
|
theme->getElement("system", "image_logo", "image")};
|
2022-02-06 13:01:40 +00:00
|
|
|
|
|
|
|
|
if (logoElem) {
|
2022-02-07 20:05:56 +00:00
|
|
|
std::string path;
|
|
|
|
|
if (logoElem->has("path"))
|
|
|
|
|
path = logoElem->get<std::string>("path");
|
2022-02-06 13:01:40 +00:00
|
|
|
std::string defaultPath {
|
|
|
|
|
logoElem->has("default") ? logoElem->get<std::string>("default") : ""};
|
|
|
|
|
if ((!path.empty() && ResourceManager::getInstance().fileExists(path)) ||
|
|
|
|
|
(!defaultPath.empty() && ResourceManager::getInstance().fileExists(defaultPath))) {
|
|
|
|
|
auto logo = std::make_shared<ImageComponent>(false, false);
|
2022-03-17 18:34:41 +00:00
|
|
|
logo->setLinearInterpolation(true);
|
2022-02-07 20:05:56 +00:00
|
|
|
logo->setMaxSize(glm::round(mLogoSize * mLogoScale));
|
|
|
|
|
logo->applyTheme(theme, "system", "image_logo",
|
|
|
|
|
ThemeFlags::PATH | ThemeFlags::COLOR);
|
2022-02-06 13:01:40 +00:00
|
|
|
logo->setRotateByTargetSize(true);
|
|
|
|
|
entry.data.logo = logo;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-07 20:05:56 +00:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (entry.data.logoPath != "" &&
|
|
|
|
|
ResourceManager::getInstance().fileExists(entry.data.logoPath)) {
|
|
|
|
|
auto logo = std::make_shared<ImageComponent>(false, false);
|
2022-03-17 18:34:41 +00:00
|
|
|
logo->setLinearInterpolation(true);
|
2022-02-13 10:45:06 +00:00
|
|
|
logo->setImage(entry.data.logoPath);
|
2022-02-07 20:05:56 +00:00
|
|
|
logo->setMaxSize(glm::round(mLogoSize * mLogoScale));
|
|
|
|
|
logo->applyTheme(theme, "system", "", ThemeFlags::ALL);
|
|
|
|
|
logo->setRotateByTargetSize(true);
|
|
|
|
|
entry.data.logo = logo;
|
2022-02-06 13:01:40 +00:00
|
|
|
}
|
2022-02-07 20:05:56 +00:00
|
|
|
else if (entry.data.defaultLogoPath != "" &&
|
|
|
|
|
ResourceManager::getInstance().fileExists(entry.data.defaultLogoPath)) {
|
|
|
|
|
auto defaultLogo = std::make_shared<ImageComponent>(false, false);
|
2022-03-17 18:34:41 +00:00
|
|
|
defaultLogo->setLinearInterpolation(true);
|
2022-02-13 10:45:06 +00:00
|
|
|
defaultLogo->setImage(entry.data.defaultLogoPath);
|
2022-02-07 20:05:56 +00:00
|
|
|
defaultLogo->setMaxSize(glm::round(mLogoSize * mLogoScale));
|
|
|
|
|
defaultLogo->applyTheme(theme, "system", "", ThemeFlags::ALL);
|
|
|
|
|
defaultLogo->setRotateByTargetSize(true);
|
|
|
|
|
entry.data.logo = defaultLogo;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!entry.data.logo) {
|
|
|
|
|
// If no logo image is present, add logo text as fallback.
|
|
|
|
|
auto text = std::make_shared<TextComponent>(entry.name, mFont, 0x000000FF, ALIGN_CENTER);
|
|
|
|
|
text->setSize(mLogoSize * mLogoScale);
|
|
|
|
|
if (legacyMode) {
|
2022-02-06 13:01:40 +00:00
|
|
|
text->applyTheme(theme, "system", "text_logoText",
|
|
|
|
|
ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR |
|
2022-02-09 21:06:34 +00:00
|
|
|
ThemeFlags::LETTER_CASE | ThemeFlags::FORCE_UPPERCASE |
|
|
|
|
|
ThemeFlags::LINE_SPACING | ThemeFlags::TEXT);
|
2022-02-07 20:05:56 +00:00
|
|
|
}
|
|
|
|
|
if (!legacyMode) {
|
|
|
|
|
text->setLineSpacing(mLineSpacing);
|
|
|
|
|
if (mText != "")
|
|
|
|
|
text->setValue(mText);
|
|
|
|
|
text->setColor(mTextColor);
|
|
|
|
|
text->setBackgroundColor(mTextBackgroundColor);
|
|
|
|
|
text->setRenderBackground(true);
|
|
|
|
|
}
|
|
|
|
|
entry.data.logo = text;
|
2022-02-06 13:01:40 +00:00
|
|
|
|
2022-02-10 23:19:08 +00:00
|
|
|
text->setHorizontalAlignment(mLogoHorizontalAlignment);
|
|
|
|
|
text->setVerticalAlignment(mLogoVerticalAlignment);
|
2022-02-06 13:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
2022-02-10 23:19:08 +00:00
|
|
|
// Set origin for the logos based on their alignment so they line up properly.
|
|
|
|
|
if (mLogoHorizontalAlignment == ALIGN_LEFT)
|
2022-02-07 20:05:56 +00:00
|
|
|
entry.data.logo->setOrigin(0, 0.5);
|
2022-02-10 23:19:08 +00:00
|
|
|
else if (mLogoHorizontalAlignment == ALIGN_RIGHT)
|
2022-02-07 20:05:56 +00:00
|
|
|
entry.data.logo->setOrigin(1.0, 0.5);
|
|
|
|
|
else
|
|
|
|
|
entry.data.logo->setOrigin(0.5, 0.5);
|
2022-02-06 13:01:40 +00:00
|
|
|
|
2022-02-10 23:19:08 +00:00
|
|
|
if (mLogoVerticalAlignment == ALIGN_TOP)
|
|
|
|
|
entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 0);
|
|
|
|
|
else if (mLogoVerticalAlignment == ALIGN_BOTTOM)
|
|
|
|
|
entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 1);
|
|
|
|
|
else
|
|
|
|
|
entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 0.5);
|
|
|
|
|
|
2022-02-06 13:01:40 +00:00
|
|
|
glm::vec2 denormalized {mLogoSize * entry.data.logo->getOrigin()};
|
|
|
|
|
entry.data.logo->setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f});
|
|
|
|
|
|
|
|
|
|
add(entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CarouselComponent::input(InputConfig* config, Input input)
|
|
|
|
|
{
|
|
|
|
|
if (input.value != 0) {
|
|
|
|
|
switch (mType) {
|
|
|
|
|
case VERTICAL:
|
|
|
|
|
case VERTICAL_WHEEL:
|
|
|
|
|
if (config->isMappedLike("up", input)) {
|
|
|
|
|
if (mCancelTransitionsCallback)
|
|
|
|
|
mCancelTransitionsCallback();
|
|
|
|
|
listInput(-1);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (config->isMappedLike("down", input)) {
|
|
|
|
|
if (mCancelTransitionsCallback)
|
|
|
|
|
mCancelTransitionsCallback();
|
|
|
|
|
listInput(1);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case HORIZONTAL:
|
|
|
|
|
case HORIZONTAL_WHEEL:
|
|
|
|
|
default:
|
|
|
|
|
if (config->isMappedLike("left", input)) {
|
|
|
|
|
if (mCancelTransitionsCallback)
|
|
|
|
|
mCancelTransitionsCallback();
|
|
|
|
|
listInput(-1);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (config->isMappedLike("right", input)) {
|
|
|
|
|
if (mCancelTransitionsCallback)
|
|
|
|
|
mCancelTransitionsCallback();
|
|
|
|
|
listInput(1);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (config->isMappedLike("left", input) || config->isMappedLike("right", input) ||
|
|
|
|
|
config->isMappedLike("up", input) || config->isMappedLike("down", input)) {
|
|
|
|
|
listInput(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GuiComponent::input(config, input);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-09 17:22:06 +00:00
|
|
|
void CarouselComponent::update(int deltaTime)
|
|
|
|
|
{
|
|
|
|
|
listUpdate(deltaTime);
|
|
|
|
|
GuiComponent::update(deltaTime);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-06 13:01:40 +00:00
|
|
|
void CarouselComponent::render(const glm::mat4& parentTrans)
|
|
|
|
|
{
|
|
|
|
|
glm::mat4 carouselTrans {parentTrans};
|
|
|
|
|
carouselTrans = glm::translate(carouselTrans, glm::vec3 {mPosition.x, mPosition.y, 0.0f});
|
|
|
|
|
carouselTrans = glm::translate(
|
|
|
|
|
carouselTrans, glm::vec3 {mOrigin.x * mSize.x * -1.0f, mOrigin.y * mSize.y * -1.0f, 0.0f});
|
|
|
|
|
|
2022-03-16 23:02:16 +00:00
|
|
|
mRenderer->pushClipRect(
|
|
|
|
|
glm::ivec2 {static_cast<int>(glm::clamp(std::round(carouselTrans[3].x), 0.0f,
|
|
|
|
|
mRenderer->getScreenWidth())),
|
|
|
|
|
static_cast<int>(glm::clamp(std::round(carouselTrans[3].y), 0.0f,
|
|
|
|
|
mRenderer->getScreenHeight()))},
|
|
|
|
|
glm::ivec2 {static_cast<int>(std::min(std::round(mSize.x), mRenderer->getScreenWidth())),
|
|
|
|
|
static_cast<int>(std::min(std::round(mSize.y), mRenderer->getScreenHeight()))});
|
|
|
|
|
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->setMatrix(carouselTrans);
|
2022-02-09 17:22:06 +00:00
|
|
|
|
|
|
|
|
// Background box behind logos.
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, mCarouselColor, mCarouselColorEnd,
|
|
|
|
|
mColorGradientHorizontal);
|
2022-02-06 13:01:40 +00:00
|
|
|
|
|
|
|
|
// Draw logos.
|
|
|
|
|
// logoSpacing will also include the size of the logo itself.
|
|
|
|
|
glm::vec2 logoSpacing {};
|
|
|
|
|
float xOff {0.0f};
|
|
|
|
|
float yOff {0.0f};
|
|
|
|
|
|
|
|
|
|
switch (mType) {
|
2022-02-07 20:05:56 +00:00
|
|
|
case HORIZONTAL_WHEEL:
|
|
|
|
|
case VERTICAL_WHEEL:
|
|
|
|
|
xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.y));
|
|
|
|
|
yOff = (mSize.y - mLogoSize.y) / 2.0f;
|
2022-02-06 13:01:40 +00:00
|
|
|
break;
|
2022-02-07 20:05:56 +00:00
|
|
|
case VERTICAL:
|
2022-02-06 13:01:40 +00:00
|
|
|
logoSpacing.y =
|
|
|
|
|
((mSize.y - (mLogoSize.y * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.y;
|
|
|
|
|
yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y);
|
2022-02-10 23:19:08 +00:00
|
|
|
if (mLogoHorizontalAlignment == ALIGN_LEFT)
|
2022-02-06 13:01:40 +00:00
|
|
|
xOff = mLogoSize.x / 10.0f;
|
2022-02-10 23:19:08 +00:00
|
|
|
else if (mLogoHorizontalAlignment == ALIGN_RIGHT)
|
2022-02-06 13:01:40 +00:00
|
|
|
xOff = mSize.x - (mLogoSize.x * 1.1f);
|
|
|
|
|
else
|
|
|
|
|
xOff = (mSize.x - mLogoSize.x) / 2.0f;
|
|
|
|
|
break;
|
2022-02-07 20:05:56 +00:00
|
|
|
case HORIZONTAL:
|
|
|
|
|
default:
|
2022-02-06 13:01:40 +00:00
|
|
|
logoSpacing.x =
|
|
|
|
|
((mSize.x - (mLogoSize.x * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.x;
|
|
|
|
|
xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.x));
|
2022-02-10 23:19:08 +00:00
|
|
|
if (mLogoVerticalAlignment == ALIGN_TOP)
|
2022-02-06 13:01:40 +00:00
|
|
|
yOff = mLogoSize.y / 10.0f;
|
2022-02-10 23:19:08 +00:00
|
|
|
else if (mLogoVerticalAlignment == ALIGN_BOTTOM)
|
2022-02-06 13:01:40 +00:00
|
|
|
yOff = mSize.y - (mLogoSize.y * 1.1f);
|
|
|
|
|
else
|
|
|
|
|
yOff = (mSize.y - mLogoSize.y) / 2.0f;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int center {static_cast<int>(mCamOffset)};
|
2022-03-16 23:02:16 +00:00
|
|
|
int logoInclusion {static_cast<int>(std::ceil(mMaxLogoCount / 2.0f))};
|
2022-03-17 20:52:55 +00:00
|
|
|
bool singleEntry {mEntries.size() == 1};
|
2022-02-06 13:01:40 +00:00
|
|
|
|
2022-03-16 23:02:16 +00:00
|
|
|
for (int i = center - logoInclusion; i < center + logoInclusion + 2; ++i) {
|
2022-02-06 13:01:40 +00:00
|
|
|
int index {i};
|
|
|
|
|
|
2022-03-17 20:52:55 +00:00
|
|
|
// If there is only a single system, then only render the logo once (in the center).
|
|
|
|
|
if (singleEntry) {
|
|
|
|
|
mEntries.at(0).data.logo->render(
|
|
|
|
|
glm::translate(carouselTrans, glm::vec3 {0 + xOff, 0 + yOff, 0.0f}));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-06 13:01:40 +00:00
|
|
|
while (index < 0)
|
|
|
|
|
index += static_cast<int>(mEntries.size());
|
|
|
|
|
while (index >= static_cast<int>(mEntries.size()))
|
|
|
|
|
index -= static_cast<int>(mEntries.size());
|
|
|
|
|
|
|
|
|
|
glm::mat4 logoTrans {carouselTrans};
|
|
|
|
|
logoTrans = glm::translate(
|
|
|
|
|
logoTrans, glm::vec3 {i * logoSpacing.x + xOff, i * logoSpacing.y + yOff, 0.0f});
|
|
|
|
|
|
2022-03-16 23:02:16 +00:00
|
|
|
float distance {i - mCamOffset};
|
2022-02-06 13:01:40 +00:00
|
|
|
|
2022-02-27 14:23:33 +00:00
|
|
|
float scale {1.0f + ((mLogoScale - 1.0f) * (1.0f - fabsf(distance)))};
|
2022-02-06 13:01:40 +00:00
|
|
|
scale = std::min(mLogoScale, std::max(1.0f, scale));
|
|
|
|
|
scale /= mLogoScale;
|
|
|
|
|
|
|
|
|
|
int opacity {
|
2022-02-27 14:23:33 +00:00
|
|
|
static_cast<int>(std::round(0x80 + ((0xFF - 0x80) * (1.0f - fabsf(distance)))))};
|
2022-02-06 13:01:40 +00:00
|
|
|
opacity = std::max(static_cast<int>(0x80), opacity);
|
|
|
|
|
|
2022-03-16 23:02:16 +00:00
|
|
|
const std::shared_ptr<GuiComponent>& comp {mEntries.at(index).data.logo};
|
2022-02-06 13:01:40 +00:00
|
|
|
|
|
|
|
|
if (comp == nullptr)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (mType == VERTICAL_WHEEL || mType == HORIZONTAL_WHEEL) {
|
|
|
|
|
comp->setRotationDegrees(mLogoRotation * distance);
|
|
|
|
|
comp->setRotationOrigin(mLogoRotationOrigin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When running at lower resolutions, prevent the scale-down to go all the way to the
|
|
|
|
|
// minimum value. This avoids potential single-pixel alignment issues when the logo
|
|
|
|
|
// can't be vertically placed exactly in the middle of the carousel. Although the
|
|
|
|
|
// problem theoretically exists at all resolutions, it's not visble at around 1080p
|
|
|
|
|
// and above.
|
|
|
|
|
if (std::min(Renderer::getScreenWidth(), Renderer::getScreenHeight()) < 1080.0f)
|
|
|
|
|
scale = glm::clamp(scale, 1.0f / mLogoScale + 0.01f, 1.0f);
|
|
|
|
|
|
|
|
|
|
comp->setScale(scale);
|
2022-02-11 21:10:25 +00:00
|
|
|
comp->setOpacity(static_cast<float>(opacity) / 255.0f);
|
2022-02-06 13:01:40 +00:00
|
|
|
comp->render(logoTrans);
|
|
|
|
|
}
|
2022-03-16 23:02:16 +00:00
|
|
|
mRenderer->popClipRect();
|
2022-02-06 13:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CarouselComponent::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, "carousel")};
|
|
|
|
|
|
|
|
|
|
mSize.x = Renderer::getScreenWidth();
|
|
|
|
|
mSize.y = Renderer::getScreenHeight() * 0.2325f;
|
|
|
|
|
mPosition.x = 0.0f;
|
|
|
|
|
mPosition.y = floorf(0.5f * (Renderer::getScreenHeight() - mSize.y));
|
|
|
|
|
mCarouselColor = 0xFFFFFFD8;
|
|
|
|
|
mCarouselColorEnd = 0xFFFFFFD8;
|
|
|
|
|
mDefaultZIndex = 50.0f;
|
2022-02-14 18:32:07 +00:00
|
|
|
mText = "";
|
2022-02-06 13:01:40 +00:00
|
|
|
|
|
|
|
|
if (!elem)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (elem->has("type")) {
|
2022-02-10 23:19:08 +00:00
|
|
|
const std::string type {elem->get<std::string>("type")};
|
|
|
|
|
if (type == "horizontal") {
|
|
|
|
|
mType = HORIZONTAL;
|
|
|
|
|
}
|
|
|
|
|
else if (type == "horizontal_wheel") {
|
|
|
|
|
mType = HORIZONTAL_WHEEL;
|
|
|
|
|
}
|
|
|
|
|
else if (type == "vertical") {
|
2022-02-06 13:01:40 +00:00
|
|
|
mType = VERTICAL;
|
2022-02-10 23:19:08 +00:00
|
|
|
}
|
|
|
|
|
else if (type == "vertical_wheel") {
|
2022-02-06 13:01:40 +00:00
|
|
|
mType = VERTICAL_WHEEL;
|
2022-02-10 23:19:08 +00:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
|
2022-03-18 19:31:04 +00:00
|
|
|
"<type> defined as \""
|
2022-02-10 23:19:08 +00:00
|
|
|
<< type << "\"";
|
2022-02-06 13:01:40 +00:00
|
|
|
mType = HORIZONTAL;
|
2022-02-10 23:19:08 +00:00
|
|
|
}
|
2022-02-06 13:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (elem->has("color")) {
|
|
|
|
|
mCarouselColor = elem->get<unsigned int>("color");
|
|
|
|
|
mCarouselColorEnd = mCarouselColor;
|
|
|
|
|
}
|
|
|
|
|
if (elem->has("colorEnd"))
|
|
|
|
|
mCarouselColorEnd = elem->get<unsigned int>("colorEnd");
|
2022-02-11 17:44:24 +00:00
|
|
|
|
|
|
|
|
if (elem->has("gradientType")) {
|
|
|
|
|
const std::string gradientType {elem->get<std::string>("gradientType")};
|
|
|
|
|
if (gradientType == "horizontal") {
|
|
|
|
|
mColorGradientHorizontal = true;
|
|
|
|
|
}
|
|
|
|
|
else if (gradientType == "vertical") {
|
|
|
|
|
mColorGradientHorizontal = false;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
mColorGradientHorizontal = true;
|
|
|
|
|
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
|
2022-03-18 19:31:04 +00:00
|
|
|
"<gradientType> defined as \""
|
2022-02-11 17:44:24 +00:00
|
|
|
<< gradientType << "\"";
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-06 13:01:40 +00:00
|
|
|
|
|
|
|
|
if (elem->has("logoScale"))
|
2022-02-07 20:05:56 +00:00
|
|
|
mLogoScale = glm::clamp(elem->get<float>("logoScale"), 0.5f, 3.0f);
|
|
|
|
|
if (elem->has("logoSize")) {
|
|
|
|
|
// Keep size within a 0.05 and 1.0 multiple of the screen size.
|
|
|
|
|
glm::vec2 logoSize {elem->get<glm::vec2>("logoSize")};
|
|
|
|
|
if (std::max(logoSize.x, logoSize.y) > 1.0f) {
|
|
|
|
|
logoSize /= std::max(logoSize.x, logoSize.y);
|
|
|
|
|
}
|
|
|
|
|
else if (std::min(logoSize.x, logoSize.y) < 0.005f) {
|
|
|
|
|
float ratio {std::min(logoSize.x, logoSize.y) / 0.005f};
|
|
|
|
|
logoSize /= ratio;
|
|
|
|
|
// Just an extra precaution if a crazy ratio was used.
|
|
|
|
|
logoSize.x = glm::clamp(logoSize.x, 0.005f, 1.0f);
|
|
|
|
|
logoSize.y = glm::clamp(logoSize.y, 0.005f, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
mLogoSize = logoSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight());
|
|
|
|
|
}
|
2022-03-16 23:02:16 +00:00
|
|
|
|
|
|
|
|
if (elem->has("maxLogoCount")) {
|
|
|
|
|
if (theme->isLegacyTheme())
|
|
|
|
|
mMaxLogoCount = std::ceil(glm::clamp(elem->get<float>("maxLogoCount"), 0.5f, 30.0f));
|
|
|
|
|
else
|
|
|
|
|
mMaxLogoCount = glm::clamp(elem->get<float>("maxLogoCount"), 0.5f, 30.0f);
|
|
|
|
|
}
|
2022-02-06 13:01:40 +00:00
|
|
|
|
|
|
|
|
if (elem->has("logoRotation"))
|
|
|
|
|
mLogoRotation = elem->get<float>("logoRotation");
|
|
|
|
|
if (elem->has("logoRotationOrigin"))
|
|
|
|
|
mLogoRotationOrigin = elem->get<glm::vec2>("logoRotationOrigin");
|
2022-02-10 23:19:08 +00:00
|
|
|
|
|
|
|
|
if (elem->has("logoHorizontalAlignment")) {
|
|
|
|
|
const std::string alignment {elem->get<std::string>("logoHorizontalAlignment")};
|
|
|
|
|
if (alignment == "left" && mType != HORIZONTAL) {
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_LEFT;
|
|
|
|
|
}
|
|
|
|
|
else if (alignment == "right" && mType != HORIZONTAL) {
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
else if (alignment == "center") {
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_CENTER;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
|
2022-03-18 19:31:04 +00:00
|
|
|
"<logoHorizontalAlignment> defined as \""
|
2022-02-10 23:19:08 +00:00
|
|
|
<< alignment << "\"";
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_CENTER;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (elem->has("logoVerticalAlignment")) {
|
|
|
|
|
const std::string alignment {elem->get<std::string>("logoVerticalAlignment")};
|
|
|
|
|
if (alignment == "top" && mType != VERTICAL) {
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_TOP;
|
|
|
|
|
}
|
|
|
|
|
else if (alignment == "bottom" && mType != VERTICAL) {
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_BOTTOM;
|
|
|
|
|
}
|
|
|
|
|
else if (alignment == "center") {
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_CENTER;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
|
2022-03-18 19:31:04 +00:00
|
|
|
"<logoVerticalAlignment> defined as \""
|
2022-02-10 23:19:08 +00:00
|
|
|
<< alignment << "\"";
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_CENTER;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Legacy themes only.
|
2022-02-06 13:01:40 +00:00
|
|
|
if (elem->has("logoAlignment")) {
|
2022-02-10 23:19:08 +00:00
|
|
|
const std::string alignment {elem->get<std::string>("logoAlignment")};
|
|
|
|
|
if (alignment == "left" && mType != HORIZONTAL) {
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_LEFT;
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_CENTER;
|
|
|
|
|
}
|
|
|
|
|
else if (alignment == "right" && mType != HORIZONTAL) {
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_RIGHT;
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_CENTER;
|
2022-02-07 20:05:56 +00:00
|
|
|
}
|
2022-02-10 23:19:08 +00:00
|
|
|
else if (alignment == "top" && mType != VERTICAL) {
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_TOP;
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_CENTER;
|
2022-02-07 20:05:56 +00:00
|
|
|
}
|
2022-02-10 23:19:08 +00:00
|
|
|
else if (alignment == "bottom" && mType != VERTICAL) {
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_BOTTOM;
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_CENTER;
|
2022-02-07 20:05:56 +00:00
|
|
|
}
|
2022-02-10 23:19:08 +00:00
|
|
|
else if (alignment == "center") {
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_CENTER;
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_CENTER;
|
2022-02-07 20:05:56 +00:00
|
|
|
}
|
|
|
|
|
else {
|
2022-02-10 23:19:08 +00:00
|
|
|
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
|
2022-03-18 19:31:04 +00:00
|
|
|
"<logoAlignment> defined as \""
|
2022-02-10 23:19:08 +00:00
|
|
|
<< alignment << "\"";
|
|
|
|
|
mLogoHorizontalAlignment = ALIGN_CENTER;
|
|
|
|
|
mLogoVerticalAlignment = ALIGN_CENTER;
|
2022-02-07 20:05:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mFont = Font::getFromTheme(elem, properties, mFont);
|
|
|
|
|
|
|
|
|
|
if (elem->has("textColor"))
|
|
|
|
|
mTextColor = elem->get<unsigned int>("textColor");
|
|
|
|
|
if (elem->has("textBackgroundColor"))
|
|
|
|
|
mTextBackgroundColor = elem->get<unsigned int>("textBackgroundColor");
|
|
|
|
|
|
|
|
|
|
if (elem->has("lineSpacing"))
|
|
|
|
|
mLineSpacing = glm::clamp(elem->get<float>("lineSpacing"), 0.5f, 3.0f);
|
|
|
|
|
|
2022-02-09 19:44:22 +00:00
|
|
|
std::string letterCase;
|
2022-02-07 20:05:56 +00:00
|
|
|
|
2022-02-09 19:44:22 +00:00
|
|
|
if (elem->has("letterCase"))
|
|
|
|
|
letterCase = elem->get<std::string>("letterCase");
|
2022-02-07 20:05:56 +00:00
|
|
|
|
|
|
|
|
if (elem->has("text")) {
|
2022-02-09 21:06:34 +00:00
|
|
|
if (letterCase == "uppercase") {
|
2022-02-07 20:05:56 +00:00
|
|
|
mText = Utils::String::toUpper(elem->get<std::string>("text"));
|
2022-02-09 21:06:34 +00:00
|
|
|
}
|
|
|
|
|
else if (letterCase == "lowercase") {
|
2022-02-07 20:05:56 +00:00
|
|
|
mText = Utils::String::toLower(elem->get<std::string>("text"));
|
2022-02-09 21:06:34 +00:00
|
|
|
}
|
|
|
|
|
else if (letterCase == "capitalize") {
|
2022-02-09 19:44:22 +00:00
|
|
|
mText = Utils::String::toCapitalized(elem->get<std::string>("text"));
|
2022-02-09 21:06:34 +00:00
|
|
|
}
|
|
|
|
|
else if (letterCase == "none") {
|
2022-02-07 20:05:56 +00:00
|
|
|
mText = elem->get<std::string>("text");
|
2022-02-09 21:06:34 +00:00
|
|
|
}
|
|
|
|
|
else {
|
2022-03-18 19:31:04 +00:00
|
|
|
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
|
|
|
|
|
"<letterCase> defined as \""
|
|
|
|
|
<< letterCase << "\"";
|
2022-02-09 21:06:34 +00:00
|
|
|
mText = elem->get<std::string>("text");
|
|
|
|
|
}
|
2022-02-06 13:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GuiComponent::applyTheme(theme, view, element, ALL);
|
2022-03-16 23:02:16 +00:00
|
|
|
|
|
|
|
|
mSize.x = glm::clamp(mSize.x, mRenderer->getScreenWidth() * 0.05f,
|
|
|
|
|
mRenderer->getScreenWidth() * 1.5f);
|
|
|
|
|
mSize.y = glm::clamp(mSize.y, mRenderer->getScreenHeight() * 0.05f,
|
|
|
|
|
mRenderer->getScreenHeight() * 1.5f);
|
2022-02-06 13:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CarouselComponent::onCursorChanged(const CursorState& state)
|
2022-02-04 20:42:08 +00:00
|
|
|
{
|
2022-02-06 13:01:40 +00:00
|
|
|
float startPos {mCamOffset};
|
|
|
|
|
float posMax {static_cast<float>(mEntries.size())};
|
|
|
|
|
float target {static_cast<float>(mCursor)};
|
|
|
|
|
|
|
|
|
|
// Find the shortest path to the target.
|
|
|
|
|
float endPos {target}; // Directly.
|
2022-02-27 14:23:33 +00:00
|
|
|
float dist {fabsf(endPos - startPos)};
|
2022-02-06 13:01:40 +00:00
|
|
|
|
2022-02-27 14:23:33 +00:00
|
|
|
if (fabsf(target + posMax - startPos - mScrollVelocity) < dist)
|
2022-02-06 13:01:40 +00:00
|
|
|
endPos = target + posMax; // Loop around the end (0 -> max).
|
2022-02-27 14:23:33 +00:00
|
|
|
if (fabsf(target - posMax - startPos - mScrollVelocity) < dist)
|
2022-02-06 13:01:40 +00:00
|
|
|
endPos = target - posMax; // Loop around the start (max - 1 -> -1).
|
|
|
|
|
|
2022-02-08 23:05:06 +00:00
|
|
|
// Make sure there are no reverse jumps between logos.
|
|
|
|
|
bool changedDirection {false};
|
|
|
|
|
if (mPreviousScrollVelocity != 0 && mPreviousScrollVelocity != mScrollVelocity)
|
|
|
|
|
changedDirection = true;
|
|
|
|
|
|
|
|
|
|
if (!changedDirection && mScrollVelocity > 0 && endPos < startPos)
|
|
|
|
|
endPos = endPos + posMax;
|
|
|
|
|
|
|
|
|
|
if (!changedDirection && mScrollVelocity < 0 && endPos > startPos)
|
|
|
|
|
endPos = endPos - posMax;
|
|
|
|
|
|
|
|
|
|
if (mScrollVelocity != 0)
|
|
|
|
|
mPreviousScrollVelocity = mScrollVelocity;
|
2022-02-06 13:01:40 +00:00
|
|
|
|
|
|
|
|
// No need to animate transition, we're not going anywhere (probably mEntries.size() == 1).
|
|
|
|
|
if (endPos == mCamOffset)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Animation* anim = new LambdaAnimation(
|
|
|
|
|
[this, startPos, endPos, posMax](float t) {
|
|
|
|
|
t -= 1;
|
2022-02-09 17:22:06 +00:00
|
|
|
float f {glm::mix(startPos, endPos, t * t * t + 1)};
|
2022-02-06 13:01:40 +00:00
|
|
|
if (f < 0)
|
|
|
|
|
f += posMax;
|
|
|
|
|
if (f >= posMax)
|
|
|
|
|
f -= posMax;
|
|
|
|
|
|
2022-02-09 17:22:06 +00:00
|
|
|
mCamOffset = f;
|
2022-02-06 13:01:40 +00:00
|
|
|
},
|
|
|
|
|
500);
|
|
|
|
|
|
|
|
|
|
setAnimation(anim, 0, nullptr, false, 0);
|
|
|
|
|
|
|
|
|
|
if (mCursorChangedCallback)
|
|
|
|
|
mCursorChangedCallback(state);
|
2022-02-04 20:42:08 +00:00
|
|
|
}
|