#include "views/SystemView.h" #include "SystemData.h" #include "Renderer.h" #include "Log.h" #include "Window.h" #include "views/ViewController.h" #include "animations/LambdaAnimation.h" #include "SystemData.h" #include "Settings.h" #include "Util.h" #define SELECTED_SCALE 1.5f #define LOGO_PADDING ((logoSize().x() * (SELECTED_SCALE - 1)/2) + (mSize.x() * 0.06f)) #define BAND_HEIGHT (logoSize().y() * SELECTED_SCALE) SystemView::SystemView(Window* window) : IList(window, LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP), mSystemInfo(window, "SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER) { mCamOffset = 0; mExtrasCamOffset = 0; mExtrasFadeOpacity = 0.0f; setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); mSystemInfo.setSize(mSize.x(), mSystemInfo.getSize().y() * 1.333f); mSystemInfo.setPosition(0, (mSize.y() + BAND_HEIGHT) / 2); populate(); } void SystemView::populate() { mEntries.clear(); for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++) { const std::shared_ptr& theme = (*it)->getTheme(); Entry e; e.name = (*it)->getName(); e.object = *it; // make logo if(theme->getElement("system", "logo", "image")) { ImageComponent* logo = new ImageComponent(mWindow, false, false); logo->setMaxSize(Eigen::Vector2f(logoSize().x(), logoSize().y())); logo->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH); logo->setPosition((logoSize().x() - logo->getSize().x()) / 2, (logoSize().y() - logo->getSize().y()) / 2); // center e.data.logo = std::shared_ptr(logo); ImageComponent* logoSelected = new ImageComponent(mWindow, false, false); logoSelected->setMaxSize(Eigen::Vector2f(logoSize().x() * SELECTED_SCALE, logoSize().y() * SELECTED_SCALE * 0.70f)); logoSelected->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH); logoSelected->setPosition((logoSize().x() - logoSelected->getSize().x()) / 2, (logoSize().y() - logoSelected->getSize().y()) / 2); // center e.data.logoSelected = std::shared_ptr(logoSelected); }else{ // no logo in theme; use text TextComponent* text = new TextComponent(mWindow, (*it)->getName(), Font::get(FONT_SIZE_LARGE), 0x000000FF, ALIGN_CENTER); text->setSize(logoSize()); e.data.logo = std::shared_ptr(text); TextComponent* textSelected = new TextComponent(mWindow, (*it)->getName(), Font::get((int)(FONT_SIZE_LARGE * SELECTED_SCALE)), 0x000000FF, ALIGN_CENTER); textSelected->setSize(logoSize()); e.data.logoSelected = std::shared_ptr(textSelected); } // make background extras e.data.backgroundExtras = std::shared_ptr(new ThemeExtras(mWindow)); e.data.backgroundExtras->setExtras(ThemeData::makeExtras((*it)->getTheme(), "system", mWindow)); this->add(e); } } void SystemView::goToSystem(SystemData* system, bool animate) { setCursor(system); if(!animate) finishAnimation(0); } bool SystemView::input(InputConfig* config, Input input) { if(input.value != 0) { if(config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_r && SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug")) { LOG(LogInfo) << " Reloading SystemList view"; // reload themes for(auto it = mEntries.begin(); it != mEntries.end(); it++) it->object->loadTheme(); populate(); updateHelpPrompts(); return true; } if(config->isMappedTo("left", input)) { listInput(-1); return true; } if(config->isMappedTo("right", input)) { listInput(1); return true; } if(config->isMappedTo("a", input)) { stopScrolling(); ViewController::get()->goToGameList(getSelected()); return true; } }else{ if(config->isMappedTo("left", input) || config->isMappedTo("right", input)) listInput(0); } return GuiComponent::input(config, input); } void SystemView::update(int deltaTime) { listUpdate(deltaTime); GuiComponent::update(deltaTime); } void SystemView::onCursorChanged(const CursorState& state) { // update help style updateHelpPrompts(); float startPos = mCamOffset; float posMax = (float)mEntries.size(); float target = (float)mCursor; // what's the shortest way to get to our target? // it's one of these... float endPos = target; // directly float dist = abs(endPos - startPos); if(abs(target + posMax - startPos) < dist) endPos = target + posMax; // loop around the end (0 -> max) if(abs(target - posMax - startPos) < dist) endPos = target - posMax; // loop around the start (max - 1 -> -1) // animate mSystemInfo's opacity (fade out, wait, fade back in) cancelAnimation(1); cancelAnimation(2); const float infoStartOpacity = mSystemInfo.getOpacity() / 255.f; Animation* infoFadeOut = new LambdaAnimation( [infoStartOpacity, this] (float t) { mSystemInfo.setOpacity((unsigned char)(lerp(infoStartOpacity, 0.f, t) * 255)); }, (int)(infoStartOpacity * 150)); unsigned int gameCount = getSelected()->getGameCount(); // also change the text after we've fully faded out setAnimation(infoFadeOut, 0, [this, gameCount] { std::stringstream ss; if (getSelected()->getName() == "retropie") ss << "CONFIGURATION"; // only display a game count if there are at least 2 games else if(gameCount > 1) ss << gameCount << " GAMES AVAILABLE"; mSystemInfo.setText(ss.str()); }, false, 1); // only display a game count if there are at least 2 games if(gameCount > 1) { Animation* infoFadeIn = new LambdaAnimation( [this](float t) { mSystemInfo.setOpacity((unsigned char)(lerp(0.f, 1.f, t) * 255)); }, 300); // wait 600ms to fade in setAnimation(infoFadeIn, 2000, nullptr, false, 2); } // no need to animate transition, we're not going anywhere (probably mEntries.size() == 1) if(endPos == mCamOffset && endPos == mExtrasCamOffset) return; Animation* anim; if(Settings::getInstance()->getString("TransitionStyle") == "fade") { float startExtrasFade = mExtrasFadeOpacity; anim = new LambdaAnimation( [startExtrasFade, startPos, endPos, posMax, this](float t) { t -= 1; float f = lerp(startPos, endPos, t*t*t + 1); if(f < 0) f += posMax; if(f >= posMax) f -= posMax; this->mCamOffset = f; t += 1; if(t < 0.3f) this->mExtrasFadeOpacity = lerp(0.0f, 1.0f, t / 0.3f + startExtrasFade); else if(t < 0.7f) this->mExtrasFadeOpacity = 1.0f; else this->mExtrasFadeOpacity = lerp(1.0f, 0.0f, (t - 0.7f) / 0.3f); if(t > 0.5f) this->mExtrasCamOffset = endPos; }, 500); } else{ // slide anim = new LambdaAnimation( [startPos, endPos, posMax, this](float t) { t -= 1; float f = lerp(startPos, endPos, t*t*t + 1); if(f < 0) f += posMax; if(f >= posMax) f -= posMax; this->mCamOffset = f; this->mExtrasCamOffset = f; }, 500); } setAnimation(anim, 0, nullptr, false, 0); } void SystemView::render(const Eigen::Affine3f& parentTrans) { if(size() == 0) return; Eigen::Affine3f trans = getTransform() * parentTrans; // draw the list elements (titles, backgrounds, logos) const float logoSizeX = logoSize().x() + LOGO_PADDING; int logoCount = (int)(mSize.x() / logoSizeX) + 2; // how many logos we need to draw int center = (int)(mCamOffset); if(mEntries.size() == 1) logoCount = 1; // draw background extras Eigen::Affine3f extrasTrans = trans; int extrasCenter = (int)mExtrasCamOffset; for(int i = extrasCenter - 1; i < extrasCenter + 2; i++) { int index = i; while(index < 0) index += mEntries.size(); while(index >= (int)mEntries.size()) index -= mEntries.size(); extrasTrans.translation() = trans.translation() + Eigen::Vector3f((i - mExtrasCamOffset) * mSize.x(), 0, 0); Eigen::Vector2i clipRect = Eigen::Vector2i((int)((i - mExtrasCamOffset) * mSize.x()), 0); Renderer::pushClipRect(clipRect, mSize.cast()); mEntries.at(index).data.backgroundExtras->render(extrasTrans); Renderer::popClipRect(); } // fade extras if necessary if(mExtrasFadeOpacity) { Renderer::setMatrix(trans); Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000000 | (unsigned char)(mExtrasFadeOpacity * 255)); } // draw logos float xOff = (mSize.x() - logoSize().x())/2 - (mCamOffset * logoSizeX); float yOff = (mSize.y() - logoSize().y())/2; // background behind the logos Renderer::setMatrix(trans); Renderer::drawRect(0.f, (mSize.y() - BAND_HEIGHT) / 2, mSize.x(), BAND_HEIGHT, 0xFFFFFFD8); Eigen::Affine3f logoTrans = trans; for(int i = center - logoCount/2; i < center + logoCount/2 + 1; i++) { int index = i; while(index < 0) index += mEntries.size(); while(index >= (int)mEntries.size()) index -= mEntries.size(); logoTrans.translation() = trans.translation() + Eigen::Vector3f(i * logoSizeX + xOff, yOff, 0); if(index == mCursor) //scale our selection up { // selected const std::shared_ptr& comp = mEntries.at(index).data.logoSelected; comp->setOpacity(0xFF); comp->render(logoTrans); }else{ // not selected const std::shared_ptr& comp = mEntries.at(index).data.logo; comp->setOpacity(0x80); comp->render(logoTrans); } } Renderer::setMatrix(trans); Renderer::drawRect(mSystemInfo.getPosition().x(), mSystemInfo.getPosition().y() - 1, mSize.x(), mSystemInfo.getSize().y(), 0xDDDDDD00 | (unsigned char)(mSystemInfo.getOpacity() / 255.f * 0xD8)); mSystemInfo.render(trans); } std::vector SystemView::getHelpPrompts() { std::vector prompts; prompts.push_back(HelpPrompt("left/right", "choose")); prompts.push_back(HelpPrompt("a", "select")); return prompts; } HelpStyle SystemView::getHelpStyle() { HelpStyle style; style.applyTheme(mEntries.at(mCursor).object->getTheme(), "system"); return style; }