mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-26 08:05:38 +00:00
Migrated the carousel code from SystemView to CarouselComponent.
This commit is contained in:
parent
d564a234c1
commit
b5d49e9b43
|
@ -11,7 +11,6 @@
|
|||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "Sound.h"
|
||||
#include "SystemData.h"
|
||||
#include "UIModeController.h"
|
||||
#include "Window.h"
|
||||
#include "animations/LambdaAnimation.h"
|
||||
|
@ -31,211 +30,120 @@ namespace
|
|||
} // namespace
|
||||
|
||||
SystemView::SystemView()
|
||||
: IList<SystemViewData, SystemData*> {LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP}
|
||||
, mSystemInfo {"SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER}
|
||||
, mPreviousScrollVelocity {0}
|
||||
: mCamOffset {0.0f}
|
||||
, mFadeOpacity {0.0f}
|
||||
, mUpdatedGameCount {false}
|
||||
, mViewNeedsReload {true}
|
||||
, mLegacyMode {false}
|
||||
{
|
||||
mCamOffset = 0;
|
||||
mExtrasCamOffset = 0;
|
||||
mExtrasFadeOpacity = 0.0f;
|
||||
mCarousel = std::make_unique<CarouselComponent>();
|
||||
mSystemInfo = std::make_unique<TextComponent>("SYSTEM INFO", Font::get(FONT_SIZE_SMALL),
|
||||
0x33333300, ALIGN_CENTER);
|
||||
|
||||
setSize(static_cast<float>(Renderer::getScreenWidth()),
|
||||
static_cast<float>(Renderer::getScreenHeight()));
|
||||
|
||||
populate();
|
||||
|
||||
mCarousel->setCursorChangedCallback([&](const CursorState& state) { onCursorChanged(state); });
|
||||
mCarousel->setCancelTransitionsCallback(
|
||||
[&] { ViewController::getInstance()->cancelViewTransitions(); });
|
||||
}
|
||||
|
||||
SystemView::~SystemView()
|
||||
{
|
||||
// TEMPORARY
|
||||
// if (mLegacyMode) {
|
||||
// Delete any existing extras.
|
||||
for (auto entry : mEntries) {
|
||||
for (auto extra : entry.data.backgroundExtras)
|
||||
delete extra;
|
||||
entry.data.backgroundExtras.clear();
|
||||
if (mLegacyMode) {
|
||||
// Delete any existing extras.
|
||||
for (auto& entry : mElements) {
|
||||
for (auto extra : entry.legacyExtras)
|
||||
delete extra;
|
||||
entry.legacyExtras.clear();
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
void SystemView::populate()
|
||||
{
|
||||
mEntries.clear();
|
||||
auto themeSets = ThemeData::getThemeSets();
|
||||
std::map<std::string, ThemeData::ThemeSet>::const_iterator selectedSet =
|
||||
themeSets.find(Settings::getInstance()->getString("ThemeSet"));
|
||||
|
||||
assert(selectedSet != themeSets.cend());
|
||||
mLegacyMode = selectedSet->second.capabilities.legacyTheme;
|
||||
|
||||
for (auto it : SystemData::sSystemVector) {
|
||||
const std::shared_ptr<ThemeData>& theme = it->getTheme();
|
||||
|
||||
mLegacyMode = theme->isLegacyTheme();
|
||||
const std::shared_ptr<ThemeData>& theme {it->getTheme()};
|
||||
|
||||
if (mViewNeedsReload)
|
||||
getViewElements(theme);
|
||||
|
||||
Entry e;
|
||||
e.name = it->getName();
|
||||
e.object = it;
|
||||
|
||||
// Component offset. Used for positioning placeholders.
|
||||
glm::vec3 offsetLogo {0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 offsetLogoPlaceholderText {0.0f, 0.0f, 0.0f};
|
||||
|
||||
// Make logo.
|
||||
const ThemeData::ThemeElement* logoElem {
|
||||
theme->getElement("system", "image_logo", "image")};
|
||||
if (logoElem) {
|
||||
std::string path;
|
||||
if (logoElem->has("path"))
|
||||
path = logoElem->get<std::string>("path");
|
||||
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 = new ImageComponent(false, false);
|
||||
logo->setMaxSize(glm::round(mCarousel.logoSize * mCarousel.logoScale));
|
||||
logo->applyTheme(theme, "system", "image_logo",
|
||||
ThemeFlags::PATH | ThemeFlags::COLOR);
|
||||
logo->setRotateByTargetSize(true);
|
||||
e.data.logo = std::shared_ptr<GuiComponent>(logo);
|
||||
}
|
||||
}
|
||||
|
||||
// No logo available? Make placeholder.
|
||||
if (!e.data.logo) {
|
||||
|
||||
glm::vec2 resolution {static_cast<float>(Renderer::getScreenWidth()),
|
||||
static_cast<float>(Renderer::getScreenHeight())};
|
||||
glm::vec3 center {resolution.x / 2.0f, resolution.y / 2.0f, 1.0f};
|
||||
|
||||
// Placeholder Image.
|
||||
logoElem = theme->getElement("system", "image_logoPlaceholderImage", "image");
|
||||
if (logoElem) {
|
||||
auto path = logoElem->get<std::string>("path");
|
||||
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 = new ImageComponent(false, false);
|
||||
logo->applyTheme(theme, "system", "image_logoPlaceholderImage",
|
||||
ThemeFlags::ALL);
|
||||
if (!logoElem->has("size"))
|
||||
logo->setMaxSize(mCarousel.logoSize * mCarousel.logoScale);
|
||||
offsetLogo = logo->getPosition() - center;
|
||||
logo->setRotateByTargetSize(true);
|
||||
e.data.logo = std::shared_ptr<GuiComponent>(logo);
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder Text.
|
||||
const ThemeData::ThemeElement* logoPlaceholderText =
|
||||
theme->getElement("system", "text_logoPlaceholderText", "text");
|
||||
if (logoPlaceholderText) {
|
||||
// Element 'logoPlaceholderText' found in theme: place text
|
||||
auto* text = new TextComponent(it->getName(), Font::get(FONT_SIZE_LARGE),
|
||||
0x000000FF, ALIGN_CENTER);
|
||||
text->setSize(mCarousel.logoSize * mCarousel.logoScale);
|
||||
if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) {
|
||||
text->setHorizontalAlignment(mCarousel.logoAlignment);
|
||||
text->setVerticalAlignment(ALIGN_CENTER);
|
||||
}
|
||||
else {
|
||||
text->setHorizontalAlignment(ALIGN_CENTER);
|
||||
text->setVerticalAlignment(mCarousel.logoAlignment);
|
||||
}
|
||||
text->applyTheme(it->getTheme(), "system", "text_logoPlaceholderText",
|
||||
ThemeFlags::ALL);
|
||||
if (!e.data.logo) {
|
||||
e.data.logo = std::shared_ptr<GuiComponent>(text);
|
||||
offsetLogo = text->getPosition() - center;
|
||||
// text->setVisible(true);
|
||||
}
|
||||
else {
|
||||
e.data.logoPlaceholderText = std::shared_ptr<GuiComponent>(text);
|
||||
offsetLogoPlaceholderText = text->getPosition() - center;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Fallback to legacy centered placeholder text.
|
||||
auto* text = new TextComponent(it->getName(), Font::get(FONT_SIZE_LARGE),
|
||||
0x000000FF, ALIGN_CENTER);
|
||||
text->setSize(mCarousel.logoSize * mCarousel.logoScale);
|
||||
text->applyTheme(it->getTheme(), "system", "logoText",
|
||||
ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR |
|
||||
ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING |
|
||||
ThemeFlags::TEXT);
|
||||
e.data.logo = std::shared_ptr<GuiComponent>(text);
|
||||
|
||||
if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) {
|
||||
text->setHorizontalAlignment(mCarousel.logoAlignment);
|
||||
text->setVerticalAlignment(ALIGN_CENTER);
|
||||
}
|
||||
else {
|
||||
text->setHorizontalAlignment(ALIGN_CENTER);
|
||||
text->setVerticalAlignment(mCarousel.logoAlignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) {
|
||||
if (mCarousel.logoAlignment == ALIGN_LEFT)
|
||||
e.data.logo->setOrigin(0, 0.5);
|
||||
else if (mCarousel.logoAlignment == ALIGN_RIGHT)
|
||||
e.data.logo->setOrigin(1.0, 0.5);
|
||||
else
|
||||
e.data.logo->setOrigin(0.5, 0.5);
|
||||
}
|
||||
else {
|
||||
if (mCarousel.logoAlignment == ALIGN_TOP)
|
||||
e.data.logo->setOrigin(0.5, 0);
|
||||
else if (mCarousel.logoAlignment == ALIGN_BOTTOM)
|
||||
e.data.logo->setOrigin(0.5, 1);
|
||||
else
|
||||
e.data.logo->setOrigin(0.5, 0.5);
|
||||
}
|
||||
|
||||
glm::vec2 denormalized {mCarousel.logoSize * e.data.logo->getOrigin()};
|
||||
glm::vec3 v = {denormalized.x, denormalized.y, 0.0f};
|
||||
e.data.logo->setPosition(v + offsetLogo);
|
||||
if (e.data.logoPlaceholderText)
|
||||
e.data.logoPlaceholderText->setPosition(v + offsetLogoPlaceholderText);
|
||||
|
||||
if (mLegacyMode) {
|
||||
// Make background extras.
|
||||
e.data.backgroundExtras = ThemeData::makeExtras(it->getTheme(), "system");
|
||||
SystemViewElements elements;
|
||||
elements.name = it->getName();
|
||||
elements.legacyExtras = ThemeData::makeExtras(theme, "system");
|
||||
|
||||
// Sort the extras by z-index.
|
||||
std::stable_sort(
|
||||
e.data.backgroundExtras.begin(), e.data.backgroundExtras.end(),
|
||||
elements.legacyExtras.begin(), elements.legacyExtras.end(),
|
||||
[](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); });
|
||||
|
||||
mElements.emplace_back(std::move(elements));
|
||||
}
|
||||
|
||||
else {
|
||||
if (!mLegacyMode) {
|
||||
SystemViewElements elements;
|
||||
if (theme->hasView("system")) {
|
||||
elements.name = it->getName();
|
||||
for (auto& element : theme->getViewElements("system").elements) {
|
||||
if (element.second.type == "image") {
|
||||
e.data.imageComponents.push_back(std::make_shared<ImageComponent>());
|
||||
e.data.imageComponents.back()->setDefaultZIndex(30.0f);
|
||||
e.data.imageComponents.back()->applyTheme(theme, "system", element.first,
|
||||
ThemeFlags::ALL);
|
||||
if (e.data.imageComponents.back()->getMetadataField() != "")
|
||||
e.data.imageComponents.back()->setScrollHide(true);
|
||||
elements.imageComponents.emplace_back(std::make_unique<ImageComponent>());
|
||||
elements.imageComponents.back()->setDefaultZIndex(30.0f);
|
||||
elements.imageComponents.back()->applyTheme(theme, "system", element.first,
|
||||
ThemeFlags::ALL);
|
||||
if (elements.imageComponents.back()->getMetadataField() != "")
|
||||
elements.imageComponents.back()->setScrollHide(true);
|
||||
elements.children.emplace_back(elements.imageComponents.back().get());
|
||||
}
|
||||
else if (element.second.type == "text") {
|
||||
e.data.textComponents.push_back(std::make_unique<TextComponent>());
|
||||
e.data.textComponents.back()->setDefaultZIndex(40.0f);
|
||||
e.data.textComponents.back()->applyTheme(theme, "system", element.first,
|
||||
ThemeFlags::ALL);
|
||||
if (e.data.textComponents.back()->getMetadataField() != "")
|
||||
e.data.textComponents.back()->setScrollHide(true);
|
||||
elements.textComponents.push_back(std::make_unique<TextComponent>());
|
||||
elements.textComponents.back()->setDefaultZIndex(40.0f);
|
||||
elements.textComponents.back()->applyTheme(theme, "system", element.first,
|
||||
ThemeFlags::ALL);
|
||||
if (elements.textComponents.back()->getMetadataField() != "")
|
||||
elements.textComponents.back()->setScrollHide(true);
|
||||
elements.children.emplace_back(elements.textComponents.back().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elements.children.emplace_back(mCarousel.get());
|
||||
elements.children.emplace_back(mSystemInfo.get());
|
||||
|
||||
std::stable_sort(
|
||||
elements.children.begin(), elements.children.end(),
|
||||
[](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); });
|
||||
|
||||
std::stable_sort(elements.imageComponents.begin(), elements.imageComponents.end(),
|
||||
[](const std::unique_ptr<ImageComponent>& a,
|
||||
const std::unique_ptr<ImageComponent>& b) {
|
||||
return b->getZIndex() > a->getZIndex();
|
||||
});
|
||||
std::stable_sort(elements.textComponents.begin(), elements.textComponents.end(),
|
||||
[](const std::unique_ptr<TextComponent>& a,
|
||||
const std::unique_ptr<TextComponent>& b) {
|
||||
return b->getZIndex() > a->getZIndex();
|
||||
});
|
||||
mElements.emplace_back(std::move(elements));
|
||||
}
|
||||
|
||||
this->add(e);
|
||||
CarouselComponent::Entry entry;
|
||||
entry.name = it->getName();
|
||||
entry.object = it;
|
||||
|
||||
mCarousel->addEntry(theme, entry);
|
||||
}
|
||||
if (mEntries.empty()) {
|
||||
|
||||
if (mCarousel->getNumEntries() == 0) {
|
||||
// Something is wrong, there is not a single system to show, check if UI mode is not full.
|
||||
if (!UIModeController::getInstance()->isUIModeFull()) {
|
||||
Settings::getInstance()->setString("UIMode", "full");
|
||||
|
@ -249,32 +157,35 @@ void SystemView::populate()
|
|||
|
||||
void SystemView::updateGameCount()
|
||||
{
|
||||
std::pair<unsigned int, unsigned int> gameCount = getSelected()->getDisplayedGameCount();
|
||||
std::pair<unsigned int, unsigned int> gameCount =
|
||||
mCarousel->getSelected()->getDisplayedGameCount();
|
||||
std::stringstream ss;
|
||||
|
||||
if (!getSelected()->isGameSystem())
|
||||
if (!mCarousel->getSelected()->isGameSystem())
|
||||
ss << "CONFIGURATION";
|
||||
else if (getSelected()->isCollection() && (getSelected()->getName() == "favorites"))
|
||||
else if (mCarousel->getSelected()->isCollection() &&
|
||||
(mCarousel->getSelected()->getName() == "favorites"))
|
||||
ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S");
|
||||
// The "recent" gamelist has probably been trimmed after sorting, so we'll cap it at
|
||||
// its maximum limit of 50 games.
|
||||
else if (getSelected()->isCollection() && (getSelected()->getName() == "recent"))
|
||||
else if (mCarousel->getSelected()->isCollection() &&
|
||||
(mCarousel->getSelected()->getName() == "recent"))
|
||||
ss << (gameCount.first > 50 ? 50 : gameCount.first) << " GAME"
|
||||
<< (gameCount.first == 1 ? " " : "S");
|
||||
else
|
||||
ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S ") << "("
|
||||
<< gameCount.second << " FAVORITE" << (gameCount.second == 1 ? ")" : "S)");
|
||||
|
||||
mSystemInfo.setText(ss.str());
|
||||
mSystemInfo->setText(ss.str());
|
||||
}
|
||||
|
||||
void SystemView::goToSystem(SystemData* system, bool animate)
|
||||
{
|
||||
setCursor(system);
|
||||
mCarousel->setCursor(system);
|
||||
updateGameCount();
|
||||
|
||||
if (!animate)
|
||||
finishAnimation(0);
|
||||
finishSystemAnimation(0);
|
||||
}
|
||||
|
||||
bool SystemView::input(InputConfig* config, Input input)
|
||||
|
@ -287,39 +198,9 @@ bool SystemView::input(InputConfig* config, Input input)
|
|||
return true;
|
||||
}
|
||||
|
||||
switch (mCarousel.type) {
|
||||
case VERTICAL:
|
||||
case VERTICAL_WHEEL:
|
||||
if (config->isMappedLike("up", input)) {
|
||||
ViewController::getInstance()->cancelViewTransitions();
|
||||
listInput(-1);
|
||||
return true;
|
||||
}
|
||||
if (config->isMappedLike("down", input)) {
|
||||
ViewController::getInstance()->cancelViewTransitions();
|
||||
listInput(1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case HORIZONTAL:
|
||||
case HORIZONTAL_WHEEL:
|
||||
default:
|
||||
if (config->isMappedLike("left", input)) {
|
||||
ViewController::getInstance()->cancelViewTransitions();
|
||||
listInput(-1);
|
||||
return true;
|
||||
}
|
||||
if (config->isMappedLike("right", input)) {
|
||||
ViewController::getInstance()->cancelViewTransitions();
|
||||
listInput(1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (config->isMappedTo("a", input)) {
|
||||
stopScrolling();
|
||||
ViewController::getInstance()->goToGamelist(getSelected());
|
||||
mCarousel->stopScrolling();
|
||||
ViewController::getInstance()->goToGamelist(mCarousel->getSelected());
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND);
|
||||
return true;
|
||||
}
|
||||
|
@ -328,7 +209,7 @@ bool SystemView::input(InputConfig* config, Input input)
|
|||
config->isMappedTo("rightthumbstickclick", input))) {
|
||||
// Get a random system and jump to it.
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND);
|
||||
setCursor(SystemData::getRandomSystem(getSelected()));
|
||||
mCarousel->setCursor(SystemData::getRandomSystem(mCarousel->getSelected()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -343,18 +224,13 @@ bool SystemView::input(InputConfig* config, Input input)
|
|||
return true;
|
||||
}
|
||||
}
|
||||
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);
|
||||
return mCarousel->input(config, input);
|
||||
}
|
||||
|
||||
void SystemView::update(int deltaTime)
|
||||
{
|
||||
listUpdate(deltaTime);
|
||||
mCarousel->update(deltaTime);
|
||||
GuiComponent::update(deltaTime);
|
||||
}
|
||||
|
||||
|
@ -363,52 +239,29 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
|
|||
// Update help style.
|
||||
updateHelpPrompts();
|
||||
|
||||
float startPos = mCamOffset;
|
||||
float posMax = static_cast<float>(mEntries.size());
|
||||
float target = static_cast<float>(mCursor);
|
||||
int scrollVelocity {mCarousel->getScrollingVelocity()};
|
||||
|
||||
float startPos {mCamOffset};
|
||||
float posMax {static_cast<float>(mCarousel->getNumEntries())};
|
||||
float target {static_cast<float>(mCarousel->getCursor())};
|
||||
|
||||
// Find the shortest path to the target.
|
||||
float endPos = target; // Directly.
|
||||
float dist = fabs(endPos - startPos);
|
||||
float endPos {target}; // Directly.
|
||||
float dist {fabs(endPos - startPos)};
|
||||
|
||||
if (fabs(target + posMax - startPos - mScrollVelocity) < dist)
|
||||
if (fabs(target + posMax - startPos - scrollVelocity) < dist)
|
||||
endPos = target + posMax; // Loop around the end (0 -> max).
|
||||
if (fabs(target - posMax - startPos - mScrollVelocity) < dist)
|
||||
if (fabs(target - posMax - startPos - scrollVelocity) < dist)
|
||||
endPos = target - posMax; // Loop around the start (max - 1 -> -1).
|
||||
|
||||
// This logic is only needed when there are two game systems, to prevent ugly jumps back
|
||||
// an forth when selecting the same direction rapidly several times in a row.
|
||||
if (posMax == 2) {
|
||||
if (mPreviousScrollVelocity == 0)
|
||||
mPreviousScrollVelocity = mScrollVelocity;
|
||||
else if (mScrollVelocity < 0 && startPos < endPos)
|
||||
mPreviousScrollVelocity = -1;
|
||||
else if (mScrollVelocity > 0 && startPos > endPos)
|
||||
mPreviousScrollVelocity = 1;
|
||||
}
|
||||
|
||||
std::string transition_style = Settings::getInstance()->getString("TransitionStyle");
|
||||
|
||||
// To prevent ugly jumps with two systems when quickly repeating the same direction.
|
||||
if (mPreviousScrollVelocity != 0 && posMax == 2 && mScrollVelocity == mPreviousScrollVelocity) {
|
||||
if (fabs(endPos - startPos) < 0.5 || fabs(endPos - startPos) > 1.5) {
|
||||
(mScrollVelocity < 0) ? endPos -= 1 : endPos += 1;
|
||||
(mCursor == 0) ? mCursor = 1 : mCursor = 0;
|
||||
updateGameCount();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No need to animate transition, we're not going anywhere (probably mEntries.size() == 1).
|
||||
if (endPos == mCamOffset && endPos == mExtrasCamOffset)
|
||||
return;
|
||||
std::string transition_style {Settings::getInstance()->getString("TransitionStyle")};
|
||||
|
||||
Animation* anim;
|
||||
|
||||
if (transition_style == "fade") {
|
||||
float startExtrasFade = mExtrasFadeOpacity;
|
||||
float startFade {mFadeOpacity};
|
||||
anim = new LambdaAnimation(
|
||||
[this, startExtrasFade, startPos, endPos, posMax](float t) {
|
||||
[this, startFade, startPos, endPos, posMax](float t) {
|
||||
t -= 1;
|
||||
float f = glm::mix(startPos, endPos, t * t * t + 1);
|
||||
if (f < 0)
|
||||
|
@ -416,23 +269,21 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
|
|||
if (f >= posMax)
|
||||
f -= posMax;
|
||||
|
||||
this->mCamOffset = f;
|
||||
|
||||
t += 1;
|
||||
if (t < 0.3f)
|
||||
this->mExtrasFadeOpacity =
|
||||
glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startExtrasFade, 0.0f, 1.0f));
|
||||
this->mFadeOpacity =
|
||||
glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startFade, 0.0f, 1.0f));
|
||||
else if (t < 0.7f)
|
||||
this->mExtrasFadeOpacity = 1.0f;
|
||||
this->mFadeOpacity = 1.0f;
|
||||
else
|
||||
this->mExtrasFadeOpacity =
|
||||
this->mFadeOpacity =
|
||||
glm::mix(1.0f, 0.0f, glm::clamp((t - 0.6f) / 0.3f, 0.0f, 1.0f));
|
||||
|
||||
if (t > 0.5f)
|
||||
this->mExtrasCamOffset = endPos;
|
||||
this->mCamOffset = endPos;
|
||||
|
||||
// Update the game count when the entire animation has been completed.
|
||||
if (mExtrasFadeOpacity == 1.0f)
|
||||
if (mFadeOpacity == 1.0f)
|
||||
updateGameCount();
|
||||
},
|
||||
500);
|
||||
|
@ -449,10 +300,9 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
|
|||
f -= posMax;
|
||||
|
||||
this->mCamOffset = f;
|
||||
this->mExtrasCamOffset = f;
|
||||
|
||||
// Hack to make the game count being updated in the middle of the animation.
|
||||
bool update = false;
|
||||
bool update {false};
|
||||
if (endPos == -1.0f && fabs(fabs(posMax) - fabs(mCamOffset)) > 0.5f &&
|
||||
!mUpdatedGameCount) {
|
||||
update = true;
|
||||
|
@ -484,8 +334,7 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
|
|||
if (f >= posMax)
|
||||
f -= posMax;
|
||||
|
||||
this->mCamOffset = f;
|
||||
this->mExtrasCamOffset = endPos;
|
||||
this->mCamOffset = endPos;
|
||||
},
|
||||
500);
|
||||
}
|
||||
|
@ -495,46 +344,80 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
|
|||
|
||||
void SystemView::render(const glm::mat4& parentTrans)
|
||||
{
|
||||
if (size() == 0)
|
||||
if (mCarousel->getNumEntries() == 0)
|
||||
return; // Nothing to render.
|
||||
|
||||
glm::mat4 trans {getTransform() * parentTrans};
|
||||
|
||||
if (mCarousel.legacyZIndexMode) {
|
||||
// Render all extras.
|
||||
renderExtras(trans, INT16_MIN, INT16_MAX);
|
||||
// Adding texture loading buffers depending on scrolling speed and status.
|
||||
int bufferIndex {mCarousel->getScrollingVelocity() + 1};
|
||||
|
||||
// Fade the screen if we're using fade transitions and we're currently transitioning.
|
||||
// This basically renders a black rectangle on top of the currently visible extras
|
||||
// (and beneath the carousel and help prompts).
|
||||
if (mExtrasFadeOpacity)
|
||||
renderFade(trans);
|
||||
Renderer::pushClipRect(glm::ivec2 {},
|
||||
glm::ivec2 {static_cast<int>(mSize.x), static_cast<int>(mSize.y)});
|
||||
|
||||
// Always render the carousel on top so that it's not faded.
|
||||
renderCarousel(trans);
|
||||
for (int i = static_cast<int>(mCamOffset) + logoBuffersLeft[bufferIndex];
|
||||
i <= static_cast<int>(mCamOffset) + logoBuffersRight[bufferIndex]; ++i) {
|
||||
int index {i};
|
||||
while (index < 0)
|
||||
index += static_cast<int>(mCarousel->getNumEntries());
|
||||
while (index >= static_cast<int>(mCarousel->getNumEntries()))
|
||||
index -= static_cast<int>(mCarousel->getNumEntries());
|
||||
|
||||
if (mCarousel->isAnimationPlaying(0) || index == mCarousel->getCursor()) {
|
||||
glm::mat4 elementTrans {trans};
|
||||
if (mCarousel->getType() == CarouselComponent::HORIZONTAL ||
|
||||
mCarousel->getType() == CarouselComponent::HORIZONTAL_WHEEL)
|
||||
elementTrans = glm::translate(elementTrans,
|
||||
glm::vec3 {(i - mCamOffset) * mSize.x, 0.0f, 0.0f});
|
||||
else
|
||||
elementTrans = glm::translate(elementTrans,
|
||||
glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f});
|
||||
|
||||
Renderer::pushClipRect(
|
||||
glm::ivec2 {static_cast<int>(elementTrans[3].x),
|
||||
static_cast<int>(elementTrans[3].y)},
|
||||
glm::ivec2 {static_cast<int>(mSize.x), static_cast<int>(mSize.y)});
|
||||
|
||||
if (mLegacyMode && mElements.size() > static_cast<size_t>(index)) {
|
||||
for (auto element : mElements[index].legacyExtras)
|
||||
element->render(elementTrans);
|
||||
}
|
||||
else if (mElements.size() > static_cast<size_t>(index)) {
|
||||
for (auto child : mElements[index].children) {
|
||||
if (child == mCarousel.get()) {
|
||||
// Render black above anything lower than the zIndex of the carousel
|
||||
// if fade transitions are in use and we're transitioning.
|
||||
if (mFadeOpacity)
|
||||
renderFade(trans);
|
||||
child->render(trans);
|
||||
}
|
||||
else {
|
||||
child->render(elementTrans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mLegacyMode)
|
||||
mSystemInfo->render(elementTrans);
|
||||
|
||||
Renderer::popClipRect();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Render the extras that are below the carousel.
|
||||
renderExtras(trans, INT16_MIN, mCarousel.zIndex);
|
||||
|
||||
// Fade the screen if we're using fade transitions and we're currently transitioning.
|
||||
// This basically renders a black rectangle on top of the currently visible extras
|
||||
// (and beneath the carousel and help prompts).
|
||||
if (mExtrasFadeOpacity)
|
||||
if (mLegacyMode) {
|
||||
if (mFadeOpacity)
|
||||
renderFade(trans);
|
||||
|
||||
// Render the carousel.
|
||||
renderCarousel(trans);
|
||||
|
||||
// Render the rest of the extras.
|
||||
renderExtras(trans, mCarousel.zIndex, INT16_MAX);
|
||||
mCarousel->render(trans);
|
||||
}
|
||||
|
||||
Renderer::popClipRect();
|
||||
}
|
||||
|
||||
std::vector<HelpPrompt> SystemView::getHelpPrompts()
|
||||
{
|
||||
std::vector<HelpPrompt> prompts;
|
||||
if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL)
|
||||
if (mCarousel->getType() == CarouselComponent::VERTICAL ||
|
||||
mCarousel->getType() == CarouselComponent::VERTICAL_WHEEL)
|
||||
prompts.push_back(HelpPrompt("up/down", "choose"));
|
||||
else
|
||||
prompts.push_back(HelpPrompt("left/right", "choose"));
|
||||
|
@ -554,7 +437,7 @@ std::vector<HelpPrompt> SystemView::getHelpPrompts()
|
|||
HelpStyle SystemView::getHelpStyle()
|
||||
{
|
||||
HelpStyle style;
|
||||
style.applyTheme(mEntries.at(mCursor).object->getTheme(), "system");
|
||||
style.applyTheme(mCarousel->getEntry(mCarousel->getCursor()).object->getTheme(), "system");
|
||||
return style;
|
||||
}
|
||||
|
||||
|
@ -569,323 +452,33 @@ void SystemView::getViewElements(const std::shared_ptr<ThemeData>& theme)
|
|||
{
|
||||
LOG(LogDebug) << "SystemView::getViewElements()";
|
||||
|
||||
getDefaultElements();
|
||||
if (theme->hasView("system"))
|
||||
mViewNeedsReload = false;
|
||||
else
|
||||
mViewNeedsReload = true;
|
||||
|
||||
if (!theme->hasView("system"))
|
||||
return;
|
||||
mCarousel->applyTheme(theme, "system", "carousel_systemcarousel", ThemeFlags::ALL);
|
||||
|
||||
const ThemeData::ThemeElement* carouselElem =
|
||||
theme->getElement("system", "carousel_systemcarousel", "carousel");
|
||||
// System info bar.
|
||||
mSystemInfo->setSize(mSize.x, mSystemInfo->getFont()->getLetterHeight() * 2.2f);
|
||||
mSystemInfo->setPosition(0.0f, mCarousel->getPosition().y + mCarousel->getSize().y);
|
||||
mSystemInfo->setBackgroundColor(0xDDDDDDD8);
|
||||
mSystemInfo->setRenderBackground(true);
|
||||
mSystemInfo->setFont(Font::get(static_cast<int>(0.035f * mSize.y), Font::getDefaultPath()));
|
||||
mSystemInfo->setColor(0x000000FF);
|
||||
mSystemInfo->setZIndex(49.0f);
|
||||
mSystemInfo->setDefaultZIndex(49.0f);
|
||||
|
||||
if (carouselElem)
|
||||
getCarouselFromTheme(carouselElem);
|
||||
|
||||
const ThemeData::ThemeElement* sysInfoElem =
|
||||
theme->getElement("system", "text_systemInfo", "text");
|
||||
const ThemeData::ThemeElement* sysInfoElem {
|
||||
theme->getElement("system", "text_systemInfo", "text")};
|
||||
|
||||
if (sysInfoElem)
|
||||
mSystemInfo.applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL);
|
||||
|
||||
mViewNeedsReload = false;
|
||||
}
|
||||
|
||||
void SystemView::renderCarousel(const glm::mat4& trans)
|
||||
{
|
||||
// Background box behind logos.
|
||||
glm::mat4 carouselTrans {trans};
|
||||
carouselTrans =
|
||||
glm::translate(carouselTrans, glm::vec3 {mCarousel.pos.x, mCarousel.pos.y, 0.0f});
|
||||
carouselTrans = glm::translate(carouselTrans,
|
||||
glm::vec3 {mCarousel.origin.x * mCarousel.size.x * -1.0f,
|
||||
mCarousel.origin.y * mCarousel.size.y * -1.0f, 0.0f});
|
||||
|
||||
glm::vec2 clipPos {carouselTrans[3].x, carouselTrans[3].y};
|
||||
Renderer::pushClipRect(glm::ivec2 {static_cast<int>(std::round(clipPos.x)),
|
||||
static_cast<int>(std::round(clipPos.y))},
|
||||
glm::ivec2 {static_cast<int>(std::round(mCarousel.size.x)),
|
||||
static_cast<int>(std::round(mCarousel.size.y))});
|
||||
|
||||
Renderer::setMatrix(carouselTrans);
|
||||
Renderer::drawRect(0.0f, 0.0f, mCarousel.size.x, mCarousel.size.y, mCarousel.color,
|
||||
mCarousel.colorEnd, mCarousel.colorGradientHorizontal);
|
||||
|
||||
// Draw logos.
|
||||
// Note: logoSpacing will also include the size of the logo itself.
|
||||
glm::vec2 logoSpacing {};
|
||||
float xOff = 0.0f;
|
||||
float yOff = 0.0f;
|
||||
|
||||
switch (mCarousel.type) {
|
||||
case VERTICAL_WHEEL: {
|
||||
yOff = (mCarousel.size.y - mCarousel.logoSize.y) / 2.0f - (mCamOffset * logoSpacing.y);
|
||||
if (mCarousel.logoAlignment == ALIGN_LEFT)
|
||||
xOff = mCarousel.logoSize.x / 10.0f;
|
||||
else if (mCarousel.logoAlignment == ALIGN_RIGHT)
|
||||
xOff = mCarousel.size.x - (mCarousel.logoSize.x * 1.1f);
|
||||
else
|
||||
xOff = (mCarousel.size.x - mCarousel.logoSize.x) / 2.0f;
|
||||
break;
|
||||
}
|
||||
case VERTICAL: {
|
||||
logoSpacing.y = ((mCarousel.size.y - (mCarousel.logoSize.y * mCarousel.maxLogoCount)) /
|
||||
(mCarousel.maxLogoCount)) +
|
||||
mCarousel.logoSize.y;
|
||||
yOff = (mCarousel.size.y - mCarousel.logoSize.y) / 2.0f - (mCamOffset * logoSpacing.y);
|
||||
if (mCarousel.logoAlignment == ALIGN_LEFT)
|
||||
xOff = mCarousel.logoSize.x / 10.0f;
|
||||
else if (mCarousel.logoAlignment == ALIGN_RIGHT)
|
||||
xOff = mCarousel.size.x - (mCarousel.logoSize.x * 1.1f);
|
||||
else
|
||||
xOff = (mCarousel.size.x - mCarousel.logoSize.x) / 2.0f;
|
||||
break;
|
||||
}
|
||||
case HORIZONTAL_WHEEL: {
|
||||
xOff = std::round((mCarousel.size.x - mCarousel.logoSize.x) / 2.0f -
|
||||
(mCamOffset * logoSpacing.y));
|
||||
if (mCarousel.logoAlignment == ALIGN_TOP)
|
||||
yOff = mCarousel.logoSize.y / 10.0f;
|
||||
else if (mCarousel.logoAlignment == ALIGN_BOTTOM)
|
||||
yOff = mCarousel.size.y - (mCarousel.logoSize.y * 1.1f);
|
||||
else
|
||||
yOff = (mCarousel.size.y - mCarousel.logoSize.y) / 2.0f;
|
||||
break;
|
||||
}
|
||||
case HORIZONTAL: {
|
||||
}
|
||||
default: {
|
||||
logoSpacing.x = ((mCarousel.size.x - (mCarousel.logoSize.x * mCarousel.maxLogoCount)) /
|
||||
(mCarousel.maxLogoCount)) +
|
||||
mCarousel.logoSize.x;
|
||||
xOff = std::round((mCarousel.size.x - mCarousel.logoSize.x) / 2.0f -
|
||||
(mCamOffset * logoSpacing.x));
|
||||
if (mCarousel.logoAlignment == ALIGN_TOP)
|
||||
yOff = mCarousel.logoSize.y / 10.0f;
|
||||
else if (mCarousel.logoAlignment == ALIGN_BOTTOM)
|
||||
yOff = mCarousel.size.y - (mCarousel.logoSize.y * 1.1f);
|
||||
else
|
||||
yOff = (mCarousel.size.y - mCarousel.logoSize.y) / 2.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int center = static_cast<int>(mCamOffset);
|
||||
int logoCount = std::min(mCarousel.maxLogoCount, static_cast<int>(mEntries.size()));
|
||||
|
||||
// Adding texture loading buffers depending on scrolling speed and status.
|
||||
int bufferIndex = getScrollingVelocity() + 1;
|
||||
int bufferLeft = logoBuffersLeft[bufferIndex];
|
||||
int bufferRight = logoBuffersRight[bufferIndex];
|
||||
if (logoCount == 1) {
|
||||
bufferLeft = 0;
|
||||
bufferRight = 0;
|
||||
}
|
||||
|
||||
for (int i = center - logoCount / 2 + bufferLeft; // Line break.
|
||||
i <= center + logoCount / 2 + bufferRight; ++i) {
|
||||
int index = i;
|
||||
|
||||
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});
|
||||
|
||||
float distance = i - mCamOffset;
|
||||
|
||||
float scale = 1.0f + ((mCarousel.logoScale - 1.0f) * (1.0f - fabs(distance)));
|
||||
scale = std::min(mCarousel.logoScale, std::max(1.0f, scale));
|
||||
scale /= mCarousel.logoScale;
|
||||
|
||||
int opacity =
|
||||
static_cast<int>(std::round(0x80 + ((0xFF - 0x80) * (1.0f - fabs(distance)))));
|
||||
opacity = std::max(static_cast<int>(0x80), opacity);
|
||||
|
||||
const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logo;
|
||||
if (mCarousel.type == VERTICAL_WHEEL || mCarousel.type == HORIZONTAL_WHEEL) {
|
||||
comp->setRotationDegrees(mCarousel.logoRotation * distance);
|
||||
comp->setRotationOrigin(mCarousel.logoRotationOrigin);
|
||||
}
|
||||
|
||||
// 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(mSize.x, mSize.y) < 1080.0f)
|
||||
scale = glm::clamp(scale, 1.0f / mCarousel.logoScale + 0.01f, 1.0f);
|
||||
|
||||
comp->setScale(scale);
|
||||
comp->setOpacity(static_cast<unsigned char>(opacity));
|
||||
comp->render(logoTrans);
|
||||
|
||||
if (mEntries.at(index).data.logoPlaceholderText) {
|
||||
const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logoPlaceholderText;
|
||||
if (mCarousel.type == VERTICAL_WHEEL || mCarousel.type == HORIZONTAL_WHEEL) {
|
||||
comp->setRotationDegrees(mCarousel.logoRotation * distance);
|
||||
comp->setRotationOrigin(mCarousel.logoRotationOrigin);
|
||||
}
|
||||
comp->setScale(scale);
|
||||
comp->setOpacity(static_cast<unsigned char>(opacity));
|
||||
comp->render(logoTrans);
|
||||
}
|
||||
}
|
||||
Renderer::popClipRect();
|
||||
}
|
||||
|
||||
void SystemView::renderExtras(const glm::mat4& trans, float lower, float upper)
|
||||
{
|
||||
int extrasCenter = static_cast<int>(mExtrasCamOffset);
|
||||
|
||||
// Adding texture loading buffers depending on scrolling speed and status.
|
||||
int bufferIndex {getScrollingVelocity() + 1};
|
||||
|
||||
Renderer::pushClipRect(glm::ivec2 {},
|
||||
glm::ivec2 {static_cast<int>(mSize.x), static_cast<int>(mSize.y)});
|
||||
|
||||
for (int i = extrasCenter + logoBuffersLeft[bufferIndex];
|
||||
i <= extrasCenter + logoBuffersRight[bufferIndex]; ++i) {
|
||||
int index = i;
|
||||
while (index < 0)
|
||||
index += static_cast<int>(mEntries.size());
|
||||
while (index >= static_cast<int>(mEntries.size()))
|
||||
index -= static_cast<int>(mEntries.size());
|
||||
|
||||
// Only render selected system when not showing.
|
||||
if (mShowing || index == mCursor) {
|
||||
glm::mat4 extrasTrans {trans};
|
||||
if (mCarousel.type == HORIZONTAL || mCarousel.type == HORIZONTAL_WHEEL)
|
||||
extrasTrans = glm::translate(
|
||||
extrasTrans, glm::vec3 {(i - mExtrasCamOffset) * mSize.x, 0.0f, 0.0f});
|
||||
else
|
||||
extrasTrans = glm::translate(
|
||||
extrasTrans, glm::vec3 {0.0f, (i - mExtrasCamOffset) * mSize.y, 0.0f});
|
||||
|
||||
Renderer::pushClipRect(
|
||||
glm::ivec2 {static_cast<int>(extrasTrans[3].x), static_cast<int>(extrasTrans[3].y)},
|
||||
glm::ivec2 {static_cast<int>(mSize.x), static_cast<int>(mSize.y)});
|
||||
SystemViewData data = mEntries.at(index).data;
|
||||
|
||||
// Quick hack to get the new theme engine to work without using extras.
|
||||
for (auto& image : data.imageComponents) {
|
||||
image->render(extrasTrans);
|
||||
}
|
||||
for (auto& text : data.textComponents) {
|
||||
text->render(extrasTrans);
|
||||
}
|
||||
|
||||
for (unsigned int j = 0; j < data.backgroundExtras.size(); ++j) {
|
||||
GuiComponent* extra = data.backgroundExtras[j];
|
||||
if (extra->getZIndex() >= lower && extra->getZIndex() < upper)
|
||||
extra->render(extrasTrans);
|
||||
}
|
||||
mSystemInfo.render(extrasTrans);
|
||||
Renderer::popClipRect();
|
||||
}
|
||||
}
|
||||
Renderer::popClipRect();
|
||||
mSystemInfo->applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL);
|
||||
}
|
||||
|
||||
void SystemView::renderFade(const glm::mat4& trans)
|
||||
{
|
||||
unsigned int fadeColor = 0x00000000 | static_cast<unsigned char>(mExtrasFadeOpacity * 255.0f);
|
||||
unsigned int fadeColor {0x00000000 | static_cast<unsigned int>(mFadeOpacity * 255.0f)};
|
||||
Renderer::setMatrix(trans);
|
||||
Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, fadeColor, fadeColor);
|
||||
}
|
||||
|
||||
void SystemView::getDefaultElements(void)
|
||||
{
|
||||
// Carousel.
|
||||
mCarousel.type = HORIZONTAL;
|
||||
mCarousel.logoAlignment = ALIGN_CENTER;
|
||||
mCarousel.size.x = mSize.x;
|
||||
mCarousel.size.y = floorf(0.2325f * mSize.y);
|
||||
mCarousel.pos.x = 0.0f;
|
||||
mCarousel.pos.y = floorf(0.5f * (mSize.y - mCarousel.size.y));
|
||||
mCarousel.origin.x = 0.0f;
|
||||
mCarousel.origin.y = 0.0f;
|
||||
mCarousel.color = 0xFFFFFFD8;
|
||||
mCarousel.colorEnd = 0xFFFFFFD8;
|
||||
mCarousel.colorGradientHorizontal = true;
|
||||
mCarousel.logoScale = 1.2f;
|
||||
mCarousel.logoRotation = 7.5f;
|
||||
mCarousel.logoRotationOrigin.x = -5.0f;
|
||||
mCarousel.logoRotationOrigin.y = 0.5f;
|
||||
mCarousel.logoSize.x = 0.25f * mSize.x;
|
||||
mCarousel.logoSize.y = 0.155f * mSize.y;
|
||||
mCarousel.maxLogoCount = 3;
|
||||
mCarousel.zIndex = 40.0f;
|
||||
mCarousel.legacyZIndexMode = true;
|
||||
|
||||
// System info bar.
|
||||
mSystemInfo.setSize(mSize.x, mSystemInfo.getFont()->getLetterHeight() * 2.2f);
|
||||
mSystemInfo.setPosition(0.0f, mCarousel.pos.y + mCarousel.size.y);
|
||||
mSystemInfo.setBackgroundColor(0xDDDDDDD8);
|
||||
mSystemInfo.setRenderBackground(true);
|
||||
mSystemInfo.setFont(Font::get(static_cast<int>(0.035f * mSize.y), Font::getDefaultPath()));
|
||||
mSystemInfo.setColor(0x000000FF);
|
||||
mSystemInfo.setZIndex(50.0f);
|
||||
mSystemInfo.setDefaultZIndex(50.0f);
|
||||
}
|
||||
|
||||
void SystemView::getCarouselFromTheme(const ThemeData::ThemeElement* elem)
|
||||
{
|
||||
if (elem->has("type")) {
|
||||
if (!(elem->get<std::string>("type").compare("vertical")))
|
||||
mCarousel.type = VERTICAL;
|
||||
else if (!(elem->get<std::string>("type").compare("vertical_wheel")))
|
||||
mCarousel.type = VERTICAL_WHEEL;
|
||||
else if (!(elem->get<std::string>("type").compare("horizontal_wheel")))
|
||||
mCarousel.type = HORIZONTAL_WHEEL;
|
||||
else
|
||||
mCarousel.type = HORIZONTAL;
|
||||
}
|
||||
if (elem->has("size"))
|
||||
mCarousel.size = elem->get<glm::vec2>("size") * mSize;
|
||||
if (elem->has("pos"))
|
||||
mCarousel.pos = elem->get<glm::vec2>("pos") * mSize;
|
||||
if (elem->has("origin"))
|
||||
mCarousel.origin = elem->get<glm::vec2>("origin");
|
||||
if (elem->has("color")) {
|
||||
mCarousel.color = elem->get<unsigned int>("color");
|
||||
mCarousel.colorEnd = mCarousel.color;
|
||||
}
|
||||
if (elem->has("colorEnd"))
|
||||
mCarousel.colorEnd = elem->get<unsigned int>("colorEnd");
|
||||
if (elem->has("gradientType"))
|
||||
mCarousel.colorGradientHorizontal =
|
||||
!(elem->get<std::string>("gradientType").compare("horizontal"));
|
||||
if (elem->has("logoScale"))
|
||||
mCarousel.logoScale = elem->get<float>("logoScale");
|
||||
if (elem->has("logoSize"))
|
||||
mCarousel.logoSize = elem->get<glm::vec2>("logoSize") * mSize;
|
||||
if (elem->has("maxLogoCount"))
|
||||
mCarousel.maxLogoCount = static_cast<int>(std::round(elem->get<float>("maxLogoCount")));
|
||||
if (elem->has("zIndex"))
|
||||
mCarousel.zIndex = elem->get<float>("zIndex");
|
||||
if (elem->has("logoRotation"))
|
||||
mCarousel.logoRotation = elem->get<float>("logoRotation");
|
||||
if (elem->has("logoRotationOrigin"))
|
||||
mCarousel.logoRotationOrigin = elem->get<glm::vec2>("logoRotationOrigin");
|
||||
if (elem->has("logoAlignment")) {
|
||||
if (!(elem->get<std::string>("logoAlignment").compare("left")))
|
||||
mCarousel.logoAlignment = ALIGN_LEFT;
|
||||
else if (!(elem->get<std::string>("logoAlignment").compare("right")))
|
||||
mCarousel.logoAlignment = ALIGN_RIGHT;
|
||||
else if (!(elem->get<std::string>("logoAlignment").compare("top")))
|
||||
mCarousel.logoAlignment = ALIGN_TOP;
|
||||
else if (!(elem->get<std::string>("logoAlignment").compare("bottom")))
|
||||
mCarousel.logoAlignment = ALIGN_BOTTOM;
|
||||
else
|
||||
mCarousel.logoAlignment = ALIGN_CENTER;
|
||||
}
|
||||
if (elem->has("legacyZIndexMode")) {
|
||||
mCarousel.legacyZIndexMode =
|
||||
elem->get<std::string>("legacyZIndexMode").compare("true") == 0 ? true : false;
|
||||
}
|
||||
else {
|
||||
mCarousel.legacyZIndexMode = true;
|
||||
}
|
||||
}
|
|
@ -11,11 +11,10 @@
|
|||
|
||||
#include "GuiComponent.h"
|
||||
#include "Sound.h"
|
||||
#include "components/BadgeComponent.h"
|
||||
#include "SystemData.h"
|
||||
#include "components/CarouselComponent.h"
|
||||
#include "components/DateTimeComponent.h"
|
||||
#include "components/IList.h"
|
||||
#include "components/LottieComponent.h"
|
||||
#include "components/RatingComponent.h"
|
||||
#include "components/ScrollableContainer.h"
|
||||
#include "components/TextComponent.h"
|
||||
#include "components/TextListComponent.h"
|
||||
|
@ -26,105 +25,72 @@
|
|||
|
||||
class SystemData;
|
||||
|
||||
enum CarouselType : unsigned int {
|
||||
HORIZONTAL = 0,
|
||||
VERTICAL = 1,
|
||||
VERTICAL_WHEEL = 2,
|
||||
HORIZONTAL_WHEEL = 3
|
||||
struct SystemViewElements {
|
||||
std::string name;
|
||||
std::vector<GuiComponent*> legacyExtras;
|
||||
std::vector<GuiComponent*> children;
|
||||
|
||||
std::vector<std::unique_ptr<TextComponent>> textComponents;
|
||||
std::vector<std::unique_ptr<ImageComponent>> imageComponents;
|
||||
std::vector<std::unique_ptr<VideoFFmpegComponent>> videoComponents;
|
||||
std::vector<std::unique_ptr<LottieComponent>> lottieAnimComponents;
|
||||
std::vector<std::unique_ptr<ScrollableContainer>> containerComponents;
|
||||
std::vector<std::unique_ptr<TextComponent>> containerTextComponents;
|
||||
};
|
||||
|
||||
struct SystemViewData {
|
||||
std::shared_ptr<GuiComponent> logo;
|
||||
std::shared_ptr<GuiComponent> logoPlaceholderText;
|
||||
std::vector<GuiComponent*> backgroundExtras;
|
||||
|
||||
std::vector<std::shared_ptr<TextComponent>> textComponents;
|
||||
std::vector<std::shared_ptr<DateTimeComponent>> dateTimeComponents;
|
||||
std::vector<std::shared_ptr<ImageComponent>> imageComponents;
|
||||
std::vector<std::shared_ptr<VideoFFmpegComponent>> videoComponents;
|
||||
std::vector<std::shared_ptr<LottieComponent>> lottieAnimComponents;
|
||||
std::vector<std::shared_ptr<BadgeComponent>> badgeComponents;
|
||||
std::vector<std::shared_ptr<RatingComponent>> ratingComponents;
|
||||
std::vector<std::shared_ptr<ScrollableContainer>> containerComponents;
|
||||
std::vector<std::shared_ptr<TextComponent>> containerTextComponents;
|
||||
std::vector<std::shared_ptr<TextComponent>> gamelistInfoComponents;
|
||||
};
|
||||
|
||||
struct SystemViewCarousel {
|
||||
CarouselType type;
|
||||
glm::vec2 pos;
|
||||
glm::vec2 size;
|
||||
glm::vec2 origin;
|
||||
float logoScale;
|
||||
float logoRotation;
|
||||
glm::vec2 logoRotationOrigin;
|
||||
Alignment logoAlignment;
|
||||
unsigned int color;
|
||||
unsigned int colorEnd;
|
||||
bool colorGradientHorizontal;
|
||||
int maxLogoCount; // Number of logos shown on the carousel.
|
||||
glm::vec2 logoSize;
|
||||
float zIndex;
|
||||
bool legacyZIndexMode;
|
||||
};
|
||||
|
||||
class SystemView : public IList<SystemViewData, SystemData*>
|
||||
class SystemView : public GuiComponent
|
||||
{
|
||||
public:
|
||||
SystemView();
|
||||
~SystemView();
|
||||
|
||||
void onShow() override { mShowing = true; }
|
||||
void onHide() override { mShowing = false; }
|
||||
|
||||
void goToSystem(SystemData* system, bool animate);
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime) override;
|
||||
void render(const glm::mat4& parentTrans) override;
|
||||
|
||||
bool isScrolling() { return mCarousel->isScrolling(); }
|
||||
void stopScrolling() { mCarousel->stopScrolling(); }
|
||||
bool isSystemAnimationPlaying(unsigned char slot)
|
||||
{
|
||||
return mCarousel->isAnimationPlaying(slot);
|
||||
}
|
||||
void finishSystemAnimation(unsigned char slot)
|
||||
{
|
||||
finishAnimation(slot);
|
||||
mCarousel->finishAnimation(slot);
|
||||
}
|
||||
|
||||
CarouselComponent::CarouselType getCarouselType() { return mCarousel->getType(); }
|
||||
SystemData* getFirstSystem() { return mCarousel->getFirst(); }
|
||||
|
||||
void onThemeChanged(const std::shared_ptr<ThemeData>& theme);
|
||||
|
||||
std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
HelpStyle getHelpStyle() override;
|
||||
|
||||
CarouselType getCarouselType() { return mCarousel.type; }
|
||||
|
||||
protected:
|
||||
void onCursorChanged(const CursorState& state) override;
|
||||
void onScroll() override
|
||||
{
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND);
|
||||
}
|
||||
void onCursorChanged(const CursorState& state);
|
||||
|
||||
private:
|
||||
void populate();
|
||||
void updateGameCount();
|
||||
// Get the ThemeElements that make up the SystemView.
|
||||
void getViewElements(const std::shared_ptr<ThemeData>& theme);
|
||||
// Populate the system carousel with the legacy values.
|
||||
void getDefaultElements(void);
|
||||
void getCarouselFromTheme(const ThemeData::ThemeElement* elem);
|
||||
|
||||
// Render system carousel.
|
||||
void renderCarousel(const glm::mat4& parentTrans);
|
||||
// Draw background extras.
|
||||
void renderExtras(const glm::mat4& parentTrans, float lower, float upper);
|
||||
void renderFade(const glm::mat4& trans);
|
||||
|
||||
SystemViewCarousel mCarousel;
|
||||
TextComponent mSystemInfo;
|
||||
std::unique_ptr<CarouselComponent> mCarousel;
|
||||
std::unique_ptr<TextComponent> mSystemInfo;
|
||||
|
||||
std::vector<SystemViewElements> mElements;
|
||||
|
||||
// Unit is list index.
|
||||
float mCamOffset;
|
||||
float mExtrasCamOffset;
|
||||
float mExtrasFadeOpacity;
|
||||
float mFadeOpacity;
|
||||
|
||||
int mPreviousScrollVelocity;
|
||||
bool mUpdatedGameCount;
|
||||
bool mViewNeedsReload;
|
||||
bool mShowing;
|
||||
|
||||
bool mLegacyMode;
|
||||
};
|
||||
|
||||
|
|
|
@ -233,7 +233,7 @@ void ViewController::goToStart(bool playTransition)
|
|||
Settings::getInstance()->setString("StartupSystem", "");
|
||||
}
|
||||
// Get the first system entry.
|
||||
goToSystemView(getSystemListView()->getFirst(), false);
|
||||
goToSystemView(getSystemListView()->getFirstSystem(), false);
|
||||
}
|
||||
|
||||
void ViewController::ReloadAndGoToStart()
|
||||
|
@ -293,8 +293,8 @@ void ViewController::stopScrolling()
|
|||
mSystemListView->stopScrolling();
|
||||
mCurrentView->stopListScrolling();
|
||||
|
||||
if (mSystemListView->isAnimationPlaying(0))
|
||||
mSystemListView->finishAnimation(0);
|
||||
if (mSystemListView->isSystemAnimationPlaying(0))
|
||||
mSystemListView->finishSystemAnimation(0);
|
||||
}
|
||||
|
||||
int ViewController::getSystemId(SystemData* system)
|
||||
|
@ -351,16 +351,16 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition)
|
|||
if (applicationStartup) {
|
||||
mCamera = glm::translate(mCamera, -mCurrentView->getPosition());
|
||||
if (Settings::getInstance()->getString("TransitionStyle") == "slide") {
|
||||
if (getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL ||
|
||||
getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL_WHEEL)
|
||||
if (getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL ||
|
||||
getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL_WHEEL)
|
||||
mCamera[3].y += static_cast<float>(Renderer::getScreenHeight());
|
||||
else
|
||||
mCamera[3].x -= static_cast<float>(Renderer::getScreenWidth());
|
||||
updateHelpPrompts();
|
||||
}
|
||||
else if (Settings::getInstance()->getString("TransitionStyle") == "fade") {
|
||||
if (getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL ||
|
||||
getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL_WHEEL)
|
||||
if (getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL ||
|
||||
getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL_WHEEL)
|
||||
mCamera[3].y += static_cast<float>(Renderer::getScreenHeight());
|
||||
else
|
||||
mCamera[3].x += static_cast<float>(Renderer::getScreenWidth());
|
||||
|
@ -463,8 +463,8 @@ void ViewController::goToGamelist(SystemData* system)
|
|||
// Stop any scrolling, animations and camera movements.
|
||||
if (mState.viewing == SYSTEM_SELECT) {
|
||||
mSystemListView->stopScrolling();
|
||||
if (mSystemListView->isAnimationPlaying(0))
|
||||
mSystemListView->finishAnimation(0);
|
||||
if (mSystemListView->isSystemAnimationPlaying(0))
|
||||
mSystemListView->finishSystemAnimation(0);
|
||||
}
|
||||
|
||||
if (slideTransitions)
|
||||
|
@ -825,8 +825,8 @@ bool ViewController::input(InputConfig* config, Input input)
|
|||
mSystemListView->stopScrolling();
|
||||
// Finish the animation too, so that it doesn't continue
|
||||
// to play when we've closed the menu.
|
||||
if (mSystemListView->isAnimationPlaying(0))
|
||||
mSystemListView->finishAnimation(0);
|
||||
if (mSystemListView->isSystemAnimationPlaying(0))
|
||||
mSystemListView->finishSystemAnimation(0);
|
||||
// Stop the gamelist scrolling as well as it would otherwise
|
||||
// also continue to run after closing the menu.
|
||||
mCurrentView->stopListScrolling();
|
||||
|
|
|
@ -8,7 +8,451 @@
|
|||
|
||||
#include "components/CarouselComponent.h"
|
||||
|
||||
CarouselComponent::CarouselComponent()
|
||||
#include "Log.h"
|
||||
#include "animations/LambdaAnimation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Buffer values for scrolling velocity (left, stopped, right).
|
||||
const int logoBuffersLeft[] = {-5, -2, -1};
|
||||
const int logoBuffersRight[] = {1, 2, 5};
|
||||
|
||||
} // namespace
|
||||
|
||||
CarouselComponent::CarouselComponent()
|
||||
: IList<CarouselElement, SystemData*> {LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP}
|
||||
, mCamOffset {0.0f}
|
||||
, mPreviousScrollVelocity {0}
|
||||
, mType {HORIZONTAL}
|
||||
, mLogoAlignment {ALIGN_CENTER}
|
||||
, mMaxLogoCount {3}
|
||||
, mLogoSize {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f}
|
||||
, mLogoScale {1.2f}
|
||||
, mLogoRotation {7.5f}
|
||||
, mLogoRotationOrigin {-5.0f, 0.5f}
|
||||
, mCarouselColor {0}
|
||||
, mCarouselColorEnd {0}
|
||||
, mColorGradientHorizontal {true}
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void CarouselComponent::addEntry(const std::shared_ptr<ThemeData>& theme, Entry& entry)
|
||||
{
|
||||
// Make logo.
|
||||
const ThemeData::ThemeElement* logoElem {theme->getElement("system", "image_logo", "image")};
|
||||
|
||||
if (logoElem) {
|
||||
std::string path;
|
||||
if (logoElem->has("path"))
|
||||
path = logoElem->get<std::string>("path");
|
||||
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);
|
||||
logo->setMaxSize(glm::round(mLogoSize * mLogoScale));
|
||||
logo->applyTheme(theme, "system", "image_logo", ThemeFlags::PATH | ThemeFlags::COLOR);
|
||||
logo->setRotateByTargetSize(true);
|
||||
entry.data.logo = logo;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entry.data.logo) {
|
||||
glm::vec2 resolution {static_cast<float>(Renderer::getScreenWidth()),
|
||||
static_cast<float>(Renderer::getScreenHeight())};
|
||||
glm::vec3 center {resolution.x / 2.0f, resolution.y / 2.0f, 1.0f};
|
||||
|
||||
// Placeholder Image.
|
||||
logoElem = theme->getElement("system", "image_logoPlaceholderImage", "image");
|
||||
if (logoElem) {
|
||||
auto path = logoElem->get<std::string>("path");
|
||||
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);
|
||||
logo->applyTheme(theme, "system", "image_logoPlaceholderImage", ThemeFlags::ALL);
|
||||
if (!logoElem->has("size"))
|
||||
logo->setMaxSize(mLogoSize * mLogoScale);
|
||||
logo->setRotateByTargetSize(true);
|
||||
entry.data.logo = logo;
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder Text.
|
||||
const ThemeData::ThemeElement* logoPlaceholderText =
|
||||
theme->getElement("system", "text_logoPlaceholderText", "text");
|
||||
if (logoPlaceholderText) {
|
||||
// Element 'logoPlaceholderText' found in theme configuration.
|
||||
auto text = std::make_shared<TextComponent>(entry.name, Font::get(FONT_SIZE_LARGE),
|
||||
0x000000FF, ALIGN_CENTER);
|
||||
text->setSize(mLogoSize * mLogoScale);
|
||||
if (mType == VERTICAL || mType == VERTICAL_WHEEL) {
|
||||
text->setHorizontalAlignment(mLogoAlignment);
|
||||
text->setVerticalAlignment(ALIGN_CENTER);
|
||||
}
|
||||
else {
|
||||
text->setHorizontalAlignment(ALIGN_CENTER);
|
||||
text->setVerticalAlignment(mLogoAlignment);
|
||||
}
|
||||
text->applyTheme(theme, "system", "text_logoPlaceholderText",
|
||||
ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR |
|
||||
ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING |
|
||||
ThemeFlags::TEXT);
|
||||
if (!entry.data.logo)
|
||||
entry.data.logo = text;
|
||||
}
|
||||
else {
|
||||
// Fallback to legacy centered placeholder text.
|
||||
auto text = std::make_shared<TextComponent>(entry.name, Font::get(FONT_SIZE_LARGE),
|
||||
0x000000FF, ALIGN_CENTER);
|
||||
text->setSize(mLogoSize * mLogoScale);
|
||||
text->applyTheme(theme, "system", "text_logoText",
|
||||
ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR |
|
||||
ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING |
|
||||
ThemeFlags::TEXT);
|
||||
entry.data.logo = text;
|
||||
|
||||
if (mType == VERTICAL || mType == VERTICAL_WHEEL) {
|
||||
text->setHorizontalAlignment(mLogoAlignment);
|
||||
text->setVerticalAlignment(ALIGN_CENTER);
|
||||
}
|
||||
else {
|
||||
text->setHorizontalAlignment(ALIGN_CENTER);
|
||||
text->setVerticalAlignment(mLogoAlignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mType == VERTICAL || mType == VERTICAL_WHEEL) {
|
||||
if (mLogoAlignment == ALIGN_LEFT)
|
||||
entry.data.logo->setOrigin(0, 0.5);
|
||||
else if (mLogoAlignment == ALIGN_RIGHT)
|
||||
entry.data.logo->setOrigin(1.0, 0.5);
|
||||
else
|
||||
entry.data.logo->setOrigin(0.5, 0.5);
|
||||
}
|
||||
else {
|
||||
if (mLogoAlignment == ALIGN_TOP)
|
||||
entry.data.logo->setOrigin(0.5, 0);
|
||||
else if (mLogoAlignment == ALIGN_BOTTOM)
|
||||
entry.data.logo->setOrigin(0.5, 1);
|
||||
else
|
||||
entry.data.logo->setOrigin(0.5, 0.5);
|
||||
}
|
||||
|
||||
glm::vec2 denormalized {mLogoSize * entry.data.logo->getOrigin()};
|
||||
entry.data.logo->setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f});
|
||||
|
||||
add(entry);
|
||||
}
|
||||
|
||||
void CarouselComponent::update(int deltaTime)
|
||||
{
|
||||
listUpdate(deltaTime);
|
||||
GuiComponent::update(deltaTime);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void CarouselComponent::render(const glm::mat4& parentTrans)
|
||||
{
|
||||
// Background box behind logos.
|
||||
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});
|
||||
|
||||
glm::vec2 clipPos {carouselTrans[3].x, carouselTrans[3].y};
|
||||
Renderer::pushClipRect(
|
||||
glm::ivec2 {static_cast<int>(std::round(clipPos.x)),
|
||||
static_cast<int>(std::round(clipPos.y))},
|
||||
glm::ivec2 {static_cast<int>(std::round(mSize.x)), static_cast<int>(std::round(mSize.y))});
|
||||
|
||||
Renderer::setMatrix(carouselTrans);
|
||||
Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, mCarouselColor, mCarouselColorEnd,
|
||||
mColorGradientHorizontal);
|
||||
|
||||
// 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) {
|
||||
case VERTICAL_WHEEL: {
|
||||
yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y);
|
||||
if (mLogoAlignment == ALIGN_LEFT)
|
||||
xOff = mLogoSize.x / 10.0f;
|
||||
else if (mLogoAlignment == ALIGN_RIGHT)
|
||||
xOff = mSize.x - (mLogoSize.x * 1.1f);
|
||||
else
|
||||
xOff = (mSize.x - mLogoSize.x) / 2.0f;
|
||||
break;
|
||||
}
|
||||
case VERTICAL: {
|
||||
logoSpacing.y =
|
||||
((mSize.y - (mLogoSize.y * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.y;
|
||||
yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y);
|
||||
if (mLogoAlignment == ALIGN_LEFT)
|
||||
xOff = mLogoSize.x / 10.0f;
|
||||
else if (mLogoAlignment == ALIGN_RIGHT)
|
||||
xOff = mSize.x - (mLogoSize.x * 1.1f);
|
||||
else
|
||||
xOff = (mSize.x - mLogoSize.x) / 2.0f;
|
||||
break;
|
||||
}
|
||||
case HORIZONTAL_WHEEL: {
|
||||
xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.y));
|
||||
if (mLogoAlignment == ALIGN_TOP)
|
||||
yOff = mLogoSize.y / 10.0f;
|
||||
else if (mLogoAlignment == ALIGN_BOTTOM)
|
||||
yOff = mSize.y - (mLogoSize.y * 1.1f);
|
||||
else
|
||||
yOff = (mSize.y - mLogoSize.y) / 2.0f;
|
||||
break;
|
||||
}
|
||||
case HORIZONTAL: {
|
||||
}
|
||||
default: {
|
||||
logoSpacing.x =
|
||||
((mSize.x - (mLogoSize.x * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.x;
|
||||
xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.x));
|
||||
if (mLogoAlignment == ALIGN_TOP)
|
||||
yOff = mLogoSize.y / 10.0f;
|
||||
else if (mLogoAlignment == ALIGN_BOTTOM)
|
||||
yOff = mSize.y - (mLogoSize.y * 1.1f);
|
||||
else
|
||||
yOff = (mSize.y - mLogoSize.y) / 2.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int center {static_cast<int>(mCamOffset)};
|
||||
int logoCount {std::min(mMaxLogoCount, static_cast<int>(mEntries.size()))};
|
||||
|
||||
// Adding texture loading buffers depending on scrolling speed and status.
|
||||
int bufferIndex {getScrollingVelocity() + 1};
|
||||
int bufferLeft {logoBuffersLeft[bufferIndex]};
|
||||
int bufferRight {logoBuffersRight[bufferIndex]};
|
||||
if (logoCount == 1) {
|
||||
bufferLeft = 0;
|
||||
bufferRight = 0;
|
||||
}
|
||||
|
||||
for (int i = center - logoCount / 2 + bufferLeft; // Line break.
|
||||
i <= center + logoCount / 2 + bufferRight; ++i) {
|
||||
int index {i};
|
||||
|
||||
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});
|
||||
|
||||
float distance = i - mCamOffset;
|
||||
|
||||
float scale {1.0f + ((mLogoScale - 1.0f) * (1.0f - fabs(distance)))};
|
||||
scale = std::min(mLogoScale, std::max(1.0f, scale));
|
||||
scale /= mLogoScale;
|
||||
|
||||
int opacity {
|
||||
static_cast<int>(std::round(0x80 + ((0xFF - 0x80) * (1.0f - fabs(distance)))))};
|
||||
opacity = std::max(static_cast<int>(0x80), opacity);
|
||||
|
||||
const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logo;
|
||||
|
||||
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);
|
||||
comp->setOpacity(static_cast<unsigned char>(opacity));
|
||||
comp->render(logoTrans);
|
||||
}
|
||||
|
||||
Renderer::popClipRect();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!elem)
|
||||
return;
|
||||
|
||||
if (elem->has("type")) {
|
||||
if (!(elem->get<std::string>("type").compare("vertical")))
|
||||
mType = VERTICAL;
|
||||
else if (!(elem->get<std::string>("type").compare("vertical_wheel")))
|
||||
mType = VERTICAL_WHEEL;
|
||||
else if (!(elem->get<std::string>("type").compare("horizontal_wheel")))
|
||||
mType = HORIZONTAL_WHEEL;
|
||||
else
|
||||
mType = HORIZONTAL;
|
||||
}
|
||||
|
||||
if (elem->has("color")) {
|
||||
mCarouselColor = elem->get<unsigned int>("color");
|
||||
mCarouselColorEnd = mCarouselColor;
|
||||
}
|
||||
if (elem->has("colorEnd"))
|
||||
mCarouselColorEnd = elem->get<unsigned int>("colorEnd");
|
||||
if (elem->has("gradientType"))
|
||||
mColorGradientHorizontal = !(elem->get<std::string>("gradientType").compare("horizontal"));
|
||||
|
||||
if (elem->has("logoScale"))
|
||||
mLogoScale = elem->get<float>("logoScale");
|
||||
if (elem->has("logoSize"))
|
||||
mLogoSize = elem->get<glm::vec2>("logoSize") *
|
||||
glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight());
|
||||
if (elem->has("maxLogoCount"))
|
||||
mMaxLogoCount = static_cast<int>(std::round(elem->get<float>("maxLogoCount")));
|
||||
|
||||
if (elem->has("logoRotation"))
|
||||
mLogoRotation = elem->get<float>("logoRotation");
|
||||
if (elem->has("logoRotationOrigin"))
|
||||
mLogoRotationOrigin = elem->get<glm::vec2>("logoRotationOrigin");
|
||||
if (elem->has("logoAlignment")) {
|
||||
if (!(elem->get<std::string>("logoAlignment").compare("left")))
|
||||
mLogoAlignment = ALIGN_LEFT;
|
||||
else if (!(elem->get<std::string>("logoAlignment").compare("right")))
|
||||
mLogoAlignment = ALIGN_RIGHT;
|
||||
else if (!(elem->get<std::string>("logoAlignment").compare("top")))
|
||||
mLogoAlignment = ALIGN_TOP;
|
||||
else if (!(elem->get<std::string>("logoAlignment").compare("bottom")))
|
||||
mLogoAlignment = ALIGN_BOTTOM;
|
||||
else
|
||||
mLogoAlignment = ALIGN_CENTER;
|
||||
}
|
||||
|
||||
GuiComponent::applyTheme(theme, view, element, ALL);
|
||||
}
|
||||
|
||||
void CarouselComponent::onCursorChanged(const CursorState& state)
|
||||
{
|
||||
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.
|
||||
float dist {fabs(endPos - startPos)};
|
||||
|
||||
if (fabs(target + posMax - startPos - mScrollVelocity) < dist)
|
||||
endPos = target + posMax; // Loop around the end (0 -> max).
|
||||
if (fabs(target - posMax - startPos - mScrollVelocity) < dist)
|
||||
endPos = target - posMax; // Loop around the start (max - 1 -> -1).
|
||||
|
||||
// This logic is only needed when there are two game systems, to prevent ugly jumps back
|
||||
// an forth when selecting the same direction rapidly several times in a row.
|
||||
if (posMax == 2) {
|
||||
if (mPreviousScrollVelocity == 0)
|
||||
mPreviousScrollVelocity = mScrollVelocity;
|
||||
else if (mScrollVelocity < 0 && startPos < endPos)
|
||||
mPreviousScrollVelocity = -1;
|
||||
else if (mScrollVelocity > 0 && startPos > endPos)
|
||||
mPreviousScrollVelocity = 1;
|
||||
}
|
||||
if (mPreviousScrollVelocity != 0 && posMax == 2 && mScrollVelocity == mPreviousScrollVelocity) {
|
||||
if (fabs(endPos - startPos) < 0.5 || fabs(endPos - startPos) > 1.5) {
|
||||
(mScrollVelocity < 0) ? endPos -= 1 : endPos += 1;
|
||||
(mCursor == 0) ? mCursor = 1 : mCursor = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
float f = glm::mix(startPos, endPos, t * t * t + 1);
|
||||
if (f < 0)
|
||||
f += posMax;
|
||||
if (f >= posMax)
|
||||
f -= posMax;
|
||||
|
||||
this->mCamOffset = f;
|
||||
},
|
||||
500);
|
||||
|
||||
setAnimation(anim, 0, nullptr, false, 0);
|
||||
|
||||
if (mCursorChangedCallback)
|
||||
mCursorChangedCallback(state);
|
||||
}
|
||||
|
|
|
@ -7,16 +7,93 @@
|
|||
//
|
||||
|
||||
#include "GuiComponent.h"
|
||||
#include "Sound.h"
|
||||
#include "components/IList.h"
|
||||
#include "components/ImageComponent.h"
|
||||
#include "components/TextComponent.h"
|
||||
#include "resources/Font.h"
|
||||
|
||||
class SystemData;
|
||||
|
||||
#ifndef ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H
|
||||
#define ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H
|
||||
|
||||
class CarouselComponent : public GuiComponent
|
||||
struct CarouselElement {
|
||||
std::shared_ptr<GuiComponent> logo;
|
||||
};
|
||||
|
||||
class CarouselComponent : public IList<CarouselElement, SystemData*>
|
||||
{
|
||||
public:
|
||||
CarouselComponent();
|
||||
|
||||
void addElement(const std::shared_ptr<GuiComponent>& component,
|
||||
const std::string& name,
|
||||
SystemData* object)
|
||||
{
|
||||
Entry listEntry;
|
||||
listEntry.name = name;
|
||||
listEntry.object = object;
|
||||
listEntry.data.logo = component;
|
||||
add(listEntry);
|
||||
}
|
||||
|
||||
void addEntry(const std::shared_ptr<ThemeData>& theme, Entry& entry);
|
||||
Entry& getEntry(int index) { return mEntries.at(index); }
|
||||
|
||||
enum CarouselType {
|
||||
HORIZONTAL, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
|
||||
VERTICAL,
|
||||
VERTICAL_WHEEL,
|
||||
HORIZONTAL_WHEEL
|
||||
};
|
||||
|
||||
int getCursor() { return mCursor; }
|
||||
const CarouselType getType() { return mType; }
|
||||
size_t getNumEntries() { return mEntries.size(); }
|
||||
|
||||
void setCursorChangedCallback(const std::function<void(CursorState state)>& func)
|
||||
{
|
||||
mCursorChangedCallback = func;
|
||||
}
|
||||
void setCancelTransitionsCallback(const std::function<void()>& func)
|
||||
{
|
||||
mCancelTransitionsCallback = func;
|
||||
}
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime) override;
|
||||
void render(const glm::mat4& parentTrans) override;
|
||||
|
||||
void applyTheme(const std::shared_ptr<ThemeData>& theme,
|
||||
const std::string& view,
|
||||
const std::string& element,
|
||||
unsigned int properties) override;
|
||||
|
||||
protected:
|
||||
void onCursorChanged(const CursorState& state) override;
|
||||
void onScroll() override
|
||||
{
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(CursorState state)> mCursorChangedCallback;
|
||||
std::function<void()> mCancelTransitionsCallback;
|
||||
|
||||
float mCamOffset;
|
||||
int mPreviousScrollVelocity;
|
||||
|
||||
CarouselType mType;
|
||||
Alignment mLogoAlignment;
|
||||
int mMaxLogoCount;
|
||||
glm::vec2 mLogoSize;
|
||||
float mLogoScale;
|
||||
float mLogoRotation;
|
||||
glm::vec2 mLogoRotationOrigin;
|
||||
unsigned int mCarouselColor;
|
||||
unsigned int mCarouselColorEnd;
|
||||
bool mColorGradientHorizontal;
|
||||
};
|
||||
|
||||
#endif // ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H
|
||||
|
|
Loading…
Reference in a new issue