From 508ea879632baa63d4871f88440d7900166c14ff Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 8 Oct 2022 09:33:57 +0200 Subject: [PATCH 01/56] Rewrite of the text wrapping code to work with languages which generally lack spaces, like Japanese. Also implemented massive performance improvements to the text wrapping code. --- es-app/src/guis/GuiMenu.cpp | 2 +- es-core/src/components/OptionListComponent.h | 22 +- es-core/src/components/TextComponent.cpp | 122 ++------ es-core/src/components/TextComponent.h | 12 +- es-core/src/components/TextEditComponent.cpp | 45 ++- es-core/src/components/TextEditComponent.h | 2 + es-core/src/resources/Font.cpp | 297 +++++++++---------- es-core/src/resources/Font.h | 33 ++- 8 files changed, 214 insertions(+), 321 deletions(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index e9c9af7bb..b57c21259 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -296,7 +296,7 @@ void GuiMenu::openUIOptions() it != SystemData::sSystemVector.cend(); ++it) { if ((*it)->getName() != "retropie") { // If required, abbreviate the system name so it doesn't overlap the setting name. - float maxNameLength {mSize.x * 0.48f}; + float maxNameLength {mSize.x * 0.51f}; startupSystem->add((*it)->getFullName(), (*it)->getName(), Settings::getInstance()->getString("StartupSystem") == (*it)->getName(), diff --git a/es-core/src/components/OptionListComponent.h b/es-core/src/components/OptionListComponent.h index 1224078c6..615e3a35a 100644 --- a/es-core/src/components/OptionListComponent.h +++ b/es-core/src/components/OptionListComponent.h @@ -342,26 +342,8 @@ private: // A maximum length parameter has been passed and the "name" size surpasses // this value, so abbreviate the string inside the arrows. auto font = Font::get(FONT_SIZE_MEDIUM); - // Calculate with an extra dot to give some leeway. - float dotsSize {font->sizeText("....").x}; - std::string abbreviatedString {font->getTextMaxWidth( - Utils::String::toUpper(it->name), it->maxNameLength)}; - float sizeDifference {font->sizeText(Utils::String::toUpper(it->name)).x - - font->sizeText(abbreviatedString).x}; - if (sizeDifference > 0.0f) { - // It doesn't make sense to abbreviate if the number of pixels removed - // by the abbreviation is less or equal to the size of the three dots - // that would be appended to the string. - if (sizeDifference <= dotsSize) { - abbreviatedString = it->name; - } - else { - if (abbreviatedString.back() == ' ') - abbreviatedString.pop_back(); - abbreviatedString += "..."; - } - } - mText.setText(Utils::String::toUpper(abbreviatedString)); + mText.setText(Utils::String::toUpper( + font->wrapText(Utils::String::toUpper(it->name), it->maxNameLength))); } else { mText.setText(Utils::String::toUpper(it->name)); diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index f813dfd33..15c7130a5 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -236,110 +236,52 @@ void TextComponent::render(const glm::mat4& parentTrans) } } -void TextComponent::calculateExtent() -{ - if (mAutoCalcExtent.x) { - if (mUppercase) - mSize = mFont->sizeText(Utils::String::toUpper(mText), mLineSpacing); - else if (mLowercase) - mSize = mFont->sizeText(Utils::String::toLower(mText), mLineSpacing); - else if (mCapitalize) - mSize = mFont->sizeText(Utils::String::toCapitalized(mText), mLineSpacing); - else - mSize = mFont->sizeText(mText, mLineSpacing); // Original case. - } - else { - if (mAutoCalcExtent.y) { - if (mUppercase) { - mSize.y = - mFont->sizeWrappedText(Utils::String::toUpper(mText), getSize().x, mLineSpacing) - .y; - } - else if (mLowercase) { - mSize.y = - mFont->sizeWrappedText(Utils::String::toLower(mText), getSize().x, mLineSpacing) - .y; - } - else if (mCapitalize) { - mSize.y = mFont - ->sizeWrappedText(Utils::String::toCapitalized(mText), getSize().x, - mLineSpacing) - .y; - } - else { - mSize.y = mFont->sizeWrappedText(mText, getSize().x, mLineSpacing).y; - } - } - } -} - void TextComponent::onTextChanged() { if (!mVerticalAutoSizing) mVerticalAutoSizing = (mSize.x != 0.0f && mSize.y == 0.0f); - calculateExtent(); - - if (!mFont || mText.empty() || mSize.x == 0.0f || mSize.y == 0.0f) { - mTextCache.reset(); - return; - } - std::string text; - if (mUppercase) - text = Utils::String::toUpper(mText); - else if (mLowercase) - text = Utils::String::toLower(mText); - else if (mCapitalize) - text = Utils::String::toCapitalized(mText); - else - text = mText; // Original case. + if (mText != "") { + if (mUppercase) + text = Utils::String::toUpper(mText); + else if (mLowercase) + text = Utils::String::toLower(mText); + else if (mCapitalize) + text = Utils::String::toCapitalized(mText); + else + text = mText; // Original case. + } - std::shared_ptr f {mFont}; - const float lineHeight {f->getHeight(mLineSpacing)}; - const bool isMultiline {mSize.y > lineHeight}; + if (mFont && mAutoCalcExtent.x) + mSize = mFont->sizeText(text, mLineSpacing); + + if (!mFont || text.empty() || mSize.x < 0.0f) + return; + + std::shared_ptr font {mFont}; + const float lineHeight {font->getHeight(mLineSpacing)}; + const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y > lineHeight}; const bool isScrollable {mParent && mParent->isScrollable()}; - bool addAbbrev {false}; - if (!isMultiline) { - size_t newline {text.find('\n')}; - // Single line of text - stop at the first newline since it'll mess everything up. - text = text.substr(0, newline); - addAbbrev = newline != std::string::npos; - } - - glm::vec2 size {f->sizeText(text)}; - if (!isMultiline && text.size() && (size.x > mSize.x || addAbbrev)) { - // Abbreviate text. - const std::string abbrev {"..."}; - float abbrevSize {f->sizeText(abbrev).x}; - - while (text.size() && size.x + abbrevSize > mSize.x) { - size_t newSize {Utils::String::prevCursor(text, text.size())}; - text.erase(newSize, text.size() - newSize); - if (!text.empty() && text.back() == ' ') - text.pop_back(); - size = f->sizeText(text); - } - - text.append(abbrev); - mTextCache = std::shared_ptr(f->buildTextCache( - text, glm::vec2 {}, mColor, mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin)); - } - else if (isMultiline && text.size() && !isScrollable) { - const std::string wrappedText {f->wrapText( - text, mSize.x, (mVerticalAutoSizing ? 0.0f : mSize.y - lineHeight), mLineSpacing)}; - mTextCache = std::shared_ptr(f->buildTextCache(wrappedText, glm::vec2 {}, mColor, - mSize.x, mHorizontalAlignment, - mLineSpacing, mNoTopMargin)); + if (isMultiline && text.size() && !isScrollable) { + const std::string wrappedText { + font->wrapText(text, mSize.x, (mVerticalAutoSizing ? 0.0f : mSize.y - lineHeight), + mLineSpacing, isMultiline)}; + mTextCache = std::shared_ptr( + font->buildTextCache(wrappedText, glm::vec2 {0.0f, 0.0f}, mColor, mSize.x, + mHorizontalAlignment, mLineSpacing, mNoTopMargin)); } else { - mTextCache = std::shared_ptr( - f->buildTextCache(f->wrapText(text, mSize.x), glm::vec2 {0.0f, 0.0f}, mColor, mSize.x, - mHorizontalAlignment, mLineSpacing, mNoTopMargin)); + mTextCache = std::shared_ptr(font->buildTextCache( + font->wrapText(text, mSize.x, 0.0f, mLineSpacing, isMultiline), glm::vec2 {0.0f, 0.0f}, + mColor, mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin)); } + if (mAutoCalcExtent.y) + mSize.y = font->getTextSize().y; + if (mOpacity != 1.0f || mThemeOpacity != 1.0f) setOpacity(mOpacity); diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index 235da987b..a8a04be9b 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -14,13 +14,10 @@ class ThemeData; -// Used to display text. -// TextComponent::setSize(x, y) works a little differently than most components: -// * (0, 0) - Will automatically calculate a size that fits -// the text on one line (expand horizontally). -// * (x != 0, 0) - Wrap text so that it does not reach beyond x. Will -// automatically calculate a vertical size (expand vertically). -// * (x != 0, y <= fontHeight) - Will truncate text so it fits within this box. +// TextComponent sizing works in the following ways: +// setSize(0.0f, 0.0f) - Automatically sizes single-line text by expanding horizontally. +// setSize(width, 0.0f) - Limits size horizontally and automatically expands vertically. +// setSize(width, height) - Wraps and abbreviates text inside the width and height boundaries. class TextComponent : public GuiComponent { public: @@ -89,7 +86,6 @@ protected: std::shared_ptr mFont; private: - void calculateExtent(); void onColorChanged(); static inline std::vector supportedSystemdataTypes { diff --git a/es-core/src/components/TextEditComponent.cpp b/es-core/src/components/TextEditComponent.cpp index ff5406a82..29de3f5b0 100644 --- a/es-core/src/components/TextEditComponent.cpp +++ b/es-core/src/components/TextEditComponent.cpp @@ -27,6 +27,7 @@ TextEditComponent::TextEditComponent() , mBlinkTime {0} , mCursorRepeatDir {0} , mScrollOffset {0.0f, 0.0f} + , mCursorPos {0.0f, 0.0f} , mBox {":/graphics/textinput.svg"} , mFont {Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT)} { @@ -51,6 +52,9 @@ void TextEditComponent::onFocusLost() void TextEditComponent::onSizeChanged() { + if (mSize.x == 0.0f || mSize.y == 0.0f) + return; + mBox.fitTo( mSize, glm::vec3 {}, glm::vec2 {-34.0f, -32.0f - (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())}); @@ -263,9 +267,10 @@ void TextEditComponent::setCursor(size_t pos) void TextEditComponent::onTextChanged() { - std::string wrappedText = (isMultiline() ? mFont->wrapText(mText, getTextAreaSize().x) : mText); + mWrappedText = + (isMultiline() ? mFont->wrapText(mText, getTextAreaSize().x, 0.0f, 1.5f, true) : mText); mTextCache = std::unique_ptr(mFont->buildTextCache( - wrappedText, 0.0f, 0.0f, 0x77777700 | static_cast(mOpacity * 255.0f))); + mWrappedText, 0.0f, 0.0f, 0x77777700 | static_cast(mOpacity * 255.0f))); if (mCursor > static_cast(mText.length())) mCursor = static_cast(mText.length()); @@ -274,22 +279,23 @@ void TextEditComponent::onTextChanged() void TextEditComponent::onCursorChanged() { if (isMultiline()) { - glm::vec2 textSize {mFont->getWrappedTextCursorOffset(mText, getTextAreaSize().x, mCursor)}; + mCursorPos = mFont->getWrappedTextCursorOffset(mWrappedText, mCursor); // Need to scroll down? - if (mScrollOffset.y + getTextAreaSize().y < textSize.y + mFont->getHeight()) - mScrollOffset.y = textSize.y - getTextAreaSize().y + mFont->getHeight(); + if (mScrollOffset.y + getTextAreaSize().y < mCursorPos.y + mFont->getHeight()) + mScrollOffset.y = mCursorPos.y - getTextAreaSize().y + mFont->getHeight(); // Need to scroll up? - else if (mScrollOffset.y > textSize.y) - mScrollOffset.y = textSize.y; + else if (mScrollOffset.y > mCursorPos.y) + mScrollOffset.y = mCursorPos.y; } else { - glm::vec2 cursorPos {mFont->sizeText(mText.substr(0, mCursor))}; + mCursorPos = mFont->sizeText(mText.substr(0, mCursor)); + mCursorPos.y = 0.0f; - if (mScrollOffset.x + getTextAreaSize().x < cursorPos.x) - mScrollOffset.x = cursorPos.x - getTextAreaSize().x; - else if (mScrollOffset.x > cursorPos.x) - mScrollOffset.x = cursorPos.x; + if (mScrollOffset.x + getTextAreaSize().x < mCursorPos.x) + mScrollOffset.x = mCursorPos.x - getTextAreaSize().x; + else if (mScrollOffset.x > mCursorPos.x) + mScrollOffset.x = mCursorPos.x; } } @@ -323,25 +329,16 @@ void TextEditComponent::render(const glm::mat4& parentTrans) mRenderer->popClipRect(); // Draw cursor. - glm::vec2 cursorPos; - if (isMultiline()) { - cursorPos = mFont->getWrappedTextCursorOffset(mText, getTextAreaSize().x, mCursor); - } - else { - cursorPos = mFont->sizeText(mText.substr(0, mCursor)); - cursorPos[1] = 0; - } - - float cursorHeight = mFont->getHeight() * 0.8f; + float cursorHeight {mFont->getHeight() * 0.8f}; if (!mEditing) { - mRenderer->drawRect(cursorPos.x, cursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f, + mRenderer->drawRect(mCursorPos.x, mCursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f, 2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0xC7C7C7FF, 0xC7C7C7FF); } if (mEditing && mBlinkTime < BLINKTIME / 2) { - mRenderer->drawRect(cursorPos.x, cursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f, + mRenderer->drawRect(mCursorPos.x, mCursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f, 2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0x777777FF, 0x777777FF); } diff --git a/es-core/src/components/TextEditComponent.h b/es-core/src/components/TextEditComponent.h index 995e44744..36cb9f44e 100644 --- a/es-core/src/components/TextEditComponent.h +++ b/es-core/src/components/TextEditComponent.h @@ -59,6 +59,7 @@ private: Renderer* mRenderer; std::string mText; + std::string mWrappedText; std::string mTextOrig; bool mFocused; bool mEditing; @@ -70,6 +71,7 @@ private: int mCursorRepeatDir; glm::vec2 mScrollOffset; + glm::vec2 mCursorPos; NinePatchComponent mBox; diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 3a220d0d5..5178c9fd5 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -20,16 +20,17 @@ std::map, std::weak_ptr> Font::sFontMap; Font::Font(int size, const std::string& path) : mRenderer {Renderer::getInstance()} - , mSize(size) + , mFontSize(size) , mMaxGlyphHeight {0} + , mTextSize {0.0f, 0.0f} , mPath(path) { - if (mSize < 9) { - mSize = 9; + if (mFontSize < 9) { + mFontSize = 9; LOG(LogWarning) << "Requested font size too small, changing to minimum supported size"; } - else if (mSize > Renderer::getScreenHeight()) { - mSize = static_cast(Renderer::getScreenHeight()); + else if (mFontSize > Renderer::getScreenHeight()) { + mFontSize = static_cast(Renderer::getScreenHeight()); LOG(LogWarning) << "Requested font size too large, changing to maximum supported size"; } @@ -47,7 +48,7 @@ Font::~Font() { unload(ResourceManager::getInstance()); - auto fontEntry = sFontMap.find(std::pair(mPath, mSize)); + auto fontEntry = sFontMap.find(std::pair(mPath, mFontSize)); if (fontEntry != sFontMap.cend()) sFontMap.erase(fontEntry); @@ -287,186 +288,157 @@ void Font::renderTextCache(TextCache* cache) } } -std::string Font::wrapText(std::string text, float maxLength, float maxHeight, float lineSpacing) +std::string Font::wrapText(const std::string& text, + const float maxLength, + const float maxHeight, + const float lineSpacing, + const bool multiLine) { - assert(maxLength != 0.0f); - - std::string out; - std::string line; - std::string word; - std::string abbreviatedWord; - std::string temp; - - size_t space {0}; - glm::vec2 textSize {0.0f, 0.0f}; - const float dotsSize {sizeText("...").x}; + assert(maxLength > 0.0f); const float lineHeight {getHeight(lineSpacing)}; - float accumHeight {0.0f}; - const bool restrictHeight {maxHeight > 0.0f}; - bool skipLastLine {false}; - float currLineLength {0.0f}; + const float dotsWidth {sizeText("...").x}; + float accumHeight {lineHeight}; + float lineWidth {0.0f}; + float charWidth {0.0f}; + float lastSpacePos {0.0f}; + unsigned int charID {0}; + size_t cursor {0}; + size_t lastSpace {0}; + size_t spaceAccum {0}; + size_t byteCount {0}; + std::string wrappedText; + std::string charEntry; + std::vector> dotsSection; + bool addDots {false}; - // While there's text or we still have text to render. - while (text.length() > 0) { - if (restrictHeight && accumHeight > maxHeight) - break; - - space = text.find_first_of(" \t\n"); - - if (space == std::string::npos) { - space = text.length() - 1; - } - else if (restrictHeight) { - if (text.at(space) == '\n') - accumHeight += lineHeight; - } - - word = text.substr(0, space + 1); - text.erase(0, space + 1); - - temp = line + word; - - textSize = sizeText(temp); - - // If the word will fit on the line, add it to our line and continue. - if (textSize.x <= maxLength) { - line = temp; - currLineLength = textSize.x; + for (size_t i = 0; i < text.length(); ++i) { + if (text[i] == '\n') { + wrappedText.append("\n"); + accumHeight += lineHeight; + lineWidth = 0.0f; + lastSpace = 0; continue; } + + charWidth = 0.0f; + byteCount = 0; + cursor = i; + + // Needed to handle multi-byte Unicode characters. + charID = Utils::String::chars2Unicode(text, cursor); + charEntry = text.substr(i, cursor - i); + + Glyph* glyph {getGlyph(charID)}; + if (glyph != nullptr) { + charWidth = glyph->advance.x; + byteCount = cursor - i; + } else { - // If the word is too long to fit within maxLength then abbreviate it. - float wordSize {sizeText(word).x}; - if (restrictHeight && currLineLength != 0.0f && maxHeight < lineHeight && - wordSize > maxLength - textSize.x) { - // Multi-word lines. - if (maxLength - currLineLength + dotsSize < wordSize && - sizeText(line).x + dotsSize > maxLength) { - while (sizeText(line).x + dotsSize > maxLength) - line.pop_back(); + // Missing glyph. + continue; + } + + if (multiLine && (charEntry == " " || charEntry == "\t")) { + lastSpace = i; + lastSpacePos = lineWidth; + } + + if (lineWidth + charWidth <= maxLength) { + if (lineWidth + charWidth + dotsWidth > maxLength) + dotsSection.emplace_back(std::make_pair(byteCount, charWidth)); + lineWidth += charWidth; + wrappedText.append(charEntry); + } + else if (!multiLine) { + addDots = true; + break; + } + else { + if (maxHeight == 0.0f || accumHeight < maxHeight) { + // New row. + float spaceOffset {0.0f}; + if (lastSpace == wrappedText.size()) { + wrappedText.append("\n"); + } + else if (lastSpace != 0) { + if (lastSpace + spaceAccum == wrappedText.size()) + wrappedText.append("\n"); + else + wrappedText[lastSpace + spaceAccum] = '\n'; + spaceOffset = lineWidth - lastSpacePos; } else { - while (word != "" && wordSize + dotsSize > maxLength - currLineLength) { - word.pop_back(); - wordSize = sizeText(word).x; - } - - line = line + word; + if (lastSpace == 0) + ++spaceAccum; + wrappedText.append("\n"); } - - if (line.back() == ' ') - line.pop_back(); - - line.append("..."); - break; - } - if (wordSize > maxLength) { - - if (line != "" && line.back() != '\n') { - if (restrictHeight) { - if (accumHeight + lineHeight > maxHeight) - continue; - accumHeight += lineHeight; - } - line.append("\n"); + if (charEntry != " " && charEntry != "\t") { + wrappedText.append(charEntry); + lineWidth = charWidth; } - - const float cutTarget {wordSize - maxLength + dotsSize}; - float cutSize {0.0f}; - - while (word != "" && cutSize < cutTarget) { - cutSize += sizeText(word.substr(word.size() - 1)).x; - word.pop_back(); + else { + lineWidth = 0.0f; } - - word.append("..."); - line = line + word; - continue; + accumHeight += lineHeight; + lineWidth += spaceOffset; + lastSpacePos = 0.0f; + lastSpace = 0; } else { - out += line + '\n'; - if (restrictHeight) - accumHeight += lineHeight; - - if (restrictHeight && accumHeight > maxHeight) { - out.pop_back(); - skipLastLine = true; - break; - } - } - line = word; - } - } - - // Whatever's left should fit. - if (!skipLastLine) - out.append(line); - - if (restrictHeight && out.back() == '\n') - out.pop_back(); - - // If the text has been abbreviated vertically then add "..." at the end of the string. - if (restrictHeight && accumHeight > maxHeight) { - if (out.back() != '\n') { - float cutSize {0.0f}; - float cutTarget {sizeText(line).x - maxLength + dotsSize}; - while (cutSize < cutTarget) { - cutSize += sizeText(out.substr(out.size() - 1)).x; - out.pop_back(); + if (multiLine) + addDots = true; + break; } } - if (out.back() == ' ') - out.pop_back(); - out.append("..."); + + i = cursor - 1; } - return out; + if (addDots) { + if (wrappedText.back() == ' ') { + lineWidth -= sizeText(" ").x; + wrappedText.pop_back(); + } + else if (wrappedText.back() == '\t') { + lineWidth -= sizeText("\t").x; + wrappedText.pop_back(); + } + while (!dotsSection.empty() && lineWidth + dotsWidth > maxLength) { + lineWidth -= dotsSection.back().second; + wrappedText.erase(wrappedText.length() - dotsSection.back().first); + dotsSection.pop_back(); + } + if (!wrappedText.empty() && wrappedText.back() == ' ') + wrappedText.pop_back(); + wrappedText.append("..."); + } + + mTextSize = {maxLength, accumHeight}; + return wrappedText; } -glm::vec2 Font::sizeWrappedText(std::string text, float xLen, float lineSpacing) +glm::vec2 Font::getWrappedTextCursorOffset(const std::string& wrappedText, + const size_t stop, + const float lineSpacing) { - text = wrapText(text, xLen); - return sizeText(text, lineSpacing); -} - -glm::vec2 Font::getWrappedTextCursorOffset(std::string text, - float xLen, - size_t stop, - float lineSpacing) -{ - std::string wrappedText {wrapText(text, xLen)}; - float lineWidth {0.0f}; - float y {0.0f}; - - size_t wrapCursor {0}; + float yPos {0.0f}; size_t cursor {0}; + while (cursor < stop) { - unsigned int wrappedCharacter {Utils::String::chars2Unicode(wrappedText, wrapCursor)}; - unsigned int character {Utils::String::chars2Unicode(text, cursor)}; - - if (wrappedCharacter == '\n' && character != '\n') { - // This is where the wordwrap inserted a newline - // Reset lineWidth and increment y, but don't consume .a cursor character. - lineWidth = 0.0f; - y += getHeight(lineSpacing); - - cursor = Utils::String::prevCursor(text, cursor); // Unconsume. - continue; - } - + unsigned int character {Utils::String::chars2Unicode(wrappedText, cursor)}; if (character == '\n') { lineWidth = 0.0f; - y += getHeight(lineSpacing); + yPos += getHeight(lineSpacing); continue; } - Glyph* glyph = getGlyph(character); + Glyph* glyph {getGlyph(character)}; if (glyph) lineWidth += glyph->advance.x; } - return glm::vec2 {lineWidth, y}; + return glm::vec2 {lineWidth, yPos}; } float Font::getHeight(float lineSpacing) const @@ -491,7 +463,7 @@ std::shared_ptr Font::getFromTheme(const ThemeData::ThemeElement* elem, return orig; std::shared_ptr font; - int size {static_cast(orig ? orig->mSize : FONT_SIZE_MEDIUM)}; + int size {static_cast(orig ? orig->mFontSize : FONT_SIZE_MEDIUM)}; std::string path {orig ? orig->mPath : getDefaultPath()}; float sh {static_cast(Renderer::getScreenHeight())}; @@ -546,7 +518,7 @@ size_t Font::getTotalMemUsage() return total; } -Font::FontTexture::FontTexture(const int mSize) +Font::FontTexture::FontTexture(const int mFontSize) { textureId = 0; @@ -562,10 +534,11 @@ Font::FontTexture::FontTexture(const int mSize) if (screenSizeModifier < 0.45f) extraTextureSize += 4; - // It's not entirely clear if the 20 and 16 constants are correct, but they seem to provide + // It's not entirely clear if the 20 and 22 constants are correct, but they seem to provide // a texture buffer large enough to hold the fonts. This logic is obviously a hack though // and needs to be properly reviewed and improved. - textureSize = glm::ivec2 {mSize * (20 + extraTextureSize), mSize * (16 + extraTextureSize / 2)}; + textureSize = + glm::ivec2 {mFontSize * (20 + extraTextureSize), mFontSize * (22 + extraTextureSize / 2)}; // Make sure the size is not unreasonably large (which may be caused by a mistake in the // theme configuration). @@ -704,7 +677,7 @@ void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize, return; } - mTextures.push_back(FontTexture(mSize)); + mTextures.push_back(FontTexture(mFontSize)); tex_out = &mTextures.back(); tex_out->initTexture(); @@ -731,7 +704,7 @@ FT_Face Font::getFaceForChar(unsigned int id) // Otherwise, take from fallbackFonts. const std::string& path {i == 0 ? mPath : fallbackFonts.at(i - 1)}; ResourceData data {ResourceManager::getInstance().getFileData(path)}; - mFaceCache[i] = std::unique_ptr(new FontFace(std::move(data), mSize)); + mFaceCache[i] = std::unique_ptr(new FontFace(std::move(data), mFontSize)); fit = mFaceCache.find(i); } @@ -762,7 +735,7 @@ Font::Glyph* Font::getGlyph(const unsigned int id) if (FT_Load_Char(face, id, FT_LOAD_RENDER)) { LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath - << ", size " << mSize; + << ", size " << mFontSize; return nullptr; } @@ -776,7 +749,7 @@ Font::Glyph* Font::getGlyph(const unsigned int id) // size (absurdly large font size). if (tex == nullptr) { LOG(LogError) << "Couldn't create glyph for character " << id << " for font " << mPath - << ", size " << mSize << " (no suitable texture found)"; + << ", size " << mFontSize << " (no suitable texture found)"; return nullptr; } diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index 941920c96..ef6b11302 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -52,6 +52,9 @@ public: // Returns the portion of a string that fits within the passed argument maxWidth. std::string getTextMaxWidth(std::string text, float maxWidth); + // Returns the size of the overall text area. + const glm::vec2 getTextSize() { return mTextSize; } + TextCache* buildTextCache(const std::string& text, float offsetX, float offsetY, @@ -69,20 +72,17 @@ public: void renderTextCache(TextCache* cache); - // Inserts newlines into text to make it wrap properly. - std::string wrapText(std::string text, - float maxLength, - float maxHeight = 0.0f, - float lineSpacing = 1.5f); + // Inserts newlines to make text wrap properly and also abbreviates single-line text. + std::string wrapText(const std::string& text, + const float maxLength, + const float maxHeight = 0.0f, + const float lineSpacing = 1.5f, + const bool multiLine = false); - // Returns the expected size of a string after wrapping is applied. - glm::vec2 sizeWrappedText(std::string text, float xLen, float lineSpacing = 1.5f); - - // Returns the position of the cursor after moving a "cursor" amount of characters. - glm::vec2 getWrappedTextCursorOffset(std::string text, - float xLen, - size_t cursor, - float lineSpacing = 1.5f); + // Returns the position of the cursor after moving it to the stop position. + glm::vec2 getWrappedTextCursorOffset(const std::string& wrappedText, + const size_t stop, + const float lineSpacing = 1.5f); float getHeight(float lineSpacing = 1.5f) const; float getLetterHeight(); @@ -90,7 +90,7 @@ public: void reload(ResourceManager& rm) override { rebuildTextures(); } void unload(ResourceManager& rm) override { unloadTextures(); } - int getSize() const { return mSize; } + int getSize() const { return mFontSize; } const std::string& getPath() const { return mPath; } static std::string getDefaultPath() { return FONT_PATH_REGULAR; } @@ -117,7 +117,7 @@ private: glm::ivec2 writePos; int rowHeight; - FontTexture(const int mSize); + FontTexture(const int mFontSize); ~FontTexture(); bool findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out); @@ -165,8 +165,9 @@ private: std::map mGlyphMap; Glyph* getGlyph(const unsigned int id); - int mSize; + int mFontSize; int mMaxGlyphHeight; + glm::vec2 mTextSize; const std::string mPath; float getNewlineStartOffset(const std::string& text, From d927135034e6bcb02025ae552ebd16fd082ff001 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 8 Oct 2022 10:00:40 +0200 Subject: [PATCH 02/56] Fixed an issue where line breaks were included in single-line text entries. --- es-core/src/resources/Font.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 5178c9fd5..2e54a6549 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -313,6 +313,10 @@ std::string Font::wrapText(const std::string& text, for (size_t i = 0; i < text.length(); ++i) { if (text[i] == '\n') { + if (!multiLine) { + addDots = true; + break; + } wrappedText.append("\n"); accumHeight += lineHeight; lineWidth = 0.0f; From a142da3d3447c57bbf0f37f9bc624d1327a3b096 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 8 Oct 2022 10:02:12 +0200 Subject: [PATCH 03/56] Line breaks are now filtered out from systems names and system full names if specified as such in the es_systems.xml file. --- es-app/src/SystemData.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 94e856dd9..a33adf38c 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -490,8 +490,8 @@ bool SystemData::loadConfig() std::string path; std::string themeFolder; - name = system.child("name").text().get(); - fullname = system.child("fullname").text().get(); + name = Utils::String::replace(system.child("name").text().get(), "\n", ""); + fullname = Utils::String::replace(system.child("fullname").text().get(), "\n", ""); sortName = system.child("systemsortname").text().get(); path = system.child("path").text().get(); From e02adfeb7a81fa181a4d84066200af597c3292f9 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 4 Oct 2022 17:33:22 +0200 Subject: [PATCH 04/56] Explicitly set some dependencies to the Release build type on macOS and Windows. --- tools/Windows_dependencies_build_MinGW.sh | 4 ++-- tools/macOS_dependencies_build.sh | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/Windows_dependencies_build_MinGW.sh b/tools/Windows_dependencies_build_MinGW.sh index 6b1edf1ef..cfd3bad69 100644 --- a/tools/Windows_dependencies_build_MinGW.sh +++ b/tools/Windows_dependencies_build_MinGW.sh @@ -39,7 +39,7 @@ echo -e "\nBuilding FreeType" cd freetype/build rm -f CMakeCache.txt -cmake -G "MinGW Makefiles" -DBUILD_SHARED_LIBS=ON .. +cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. make clean make -j${JOBS} cp libfreetype.dll ../../.. @@ -48,7 +48,7 @@ cd ../.. echo -e "\nBuilding pugixml" cd pugixml rm -f CMakeCache.txt -cmake -G "MinGW Makefiles" -DBUILD_SHARED_LIBS=ON . +cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON . make clean make -j${JOBS} cp libpugixml.dll ../.. diff --git a/tools/macOS_dependencies_build.sh b/tools/macOS_dependencies_build.sh index db36dfcd4..7cbc3ac76 100755 --- a/tools/macOS_dependencies_build.sh +++ b/tools/macOS_dependencies_build.sh @@ -37,9 +37,9 @@ echo "Building libpng" cd libpng rm -f CMakeCache.txt if [ $(uname -m) == "arm64" ]; then - cmake -DPNG_SHARED=off -DPNG_ARM_NEON=off -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . + cmake -DCMAKE_BUILD_TYPE=Release -DPNG_SHARED=off -DPNG_ARM_NEON=off -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . else - cmake -DPNG_SHARED=off -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . + cmake -DCMAKE_BUILD_TYPE=Release -DPNG_SHARED=off -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . fi make clean make -j${JOBS} @@ -49,7 +49,7 @@ cd .. echo "\nBuilding FreeType" cd freetype/build rm -f CMakeCache.txt -cmake -DCMAKE_DISABLE_FIND_PACKAGE_HarfBuzz=on -DBUILD_SHARED_LIBS=on -DCMAKE_MACOSX_RPATH=on -DCMAKE_INSTALL_PREFIX=$(pwd)/../../local_install -S .. -B . +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_DISABLE_FIND_PACKAGE_HarfBuzz=on -DBUILD_SHARED_LIBS=on -DCMAKE_MACOSX_RPATH=on -DCMAKE_INSTALL_PREFIX=$(pwd)/../../local_install -S .. -B . make clean make -j${JOBS} cp libfreetype.6.18.0.dylib ../../../libfreetype.6.dylib @@ -65,7 +65,7 @@ cd ../.. echo "\nBuilding pugixml" cd pugixml rm -f CMakeCache.txt -cmake . +cmake -DCMAKE_BUILD_TYPE=Release . make clean make -j${JOBS} cp libpugixml.a ../.. @@ -91,7 +91,7 @@ cd .. echo "\nBuilding Ogg" cd ogg rm -f CMakeCache.txt -cmake -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . make clean make -j${JOBS} make install @@ -100,7 +100,7 @@ cd .. echo "\nBuilding Vorbis" cd vorbis rm -f CMakeCache.txt -cmake -DBUILD_SHARED_LIBS=on -DCMAKE_MACOSX_RPATH=on -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . +cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=on -DCMAKE_MACOSX_RPATH=on -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . make clean make -j${JOBS} make install @@ -111,7 +111,7 @@ cd .. echo "\nBuilding Opus" cd opus rm -f CMakeCache.txt -cmake -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$(pwd)/../local_install . make clean make -j${JOBS} make install From 660348e0d96d8deab2aadef1f150c2834377b386 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 4 Oct 2022 17:46:02 +0200 Subject: [PATCH 05/56] (Windows) Added stripping of some dependency DLL files. --- tools/Windows_dependencies_build_MinGW.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/Windows_dependencies_build_MinGW.sh b/tools/Windows_dependencies_build_MinGW.sh index cfd3bad69..8fad8bf74 100644 --- a/tools/Windows_dependencies_build_MinGW.sh +++ b/tools/Windows_dependencies_build_MinGW.sh @@ -42,6 +42,7 @@ rm -f CMakeCache.txt cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. make clean make -j${JOBS} +strip libfreetype.dll cp libfreetype.dll ../../.. cd ../.. @@ -51,5 +52,6 @@ rm -f CMakeCache.txt cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON . make clean make -j${JOBS} +strip libpugixml.dll cp libpugixml.dll ../.. cd .. From c7a035127c22b6324fa1fdcbf0a8a48ba23946ab Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 8 Oct 2022 11:04:51 +0200 Subject: [PATCH 06/56] The rlottie library is now built as optimized and without debug info on Unix and macOS. Also disabled some annoying warning messages when compiling rlottie. --- external/CMakeLists.txt | 47 ++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 518a6cabb..cbe8610c7 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -6,24 +6,55 @@ # CMake configuration for bundled dependencies built in-tree. # -# On Windows, rlottie is built as a DLL file. -if(NOT WIN32) - set(BUILD_SHARED_LIBS OFF) +# This makes it possible to set options in subprojects. +set (CMAKE_POLICY_DEFAULT_CMP0077 NEW) + +if (WIN32) + # On Windows the build type needs to match the main binary. + if (CMAKE_BUILD_TYPE MATCHES Profiling) + set (CMAKE_BUILD_TYPE Release) + elseif(NOT CMAKE_BUILD_TYPE MATCHES Debug) + set (CMAKE_BUILD_TYPE Release) + endif() +else() + # Always build with optimizations enabled and without debug info. + set (CMAKE_BUILD_TYPE Release) endif() -# Disabled threading support for rlottie as this functionality actually leads to far worse +unset(CMAKE_CXX_FLAGS) +unset(CMAKE_EXE_LINKER_FLAGS) + +if(WIN32) + if(CMAKE_CXX_COMPILER_ID MATCHES MSVC) + # Disable DLL interface warnings. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") + else() + # Strip the DLL files when building with MinGW. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") + endif() + if (WIN32) + set(BUILD_SHARED_LIBS ON) + else() + set(BUILD_SHARED_LIBS OFF) + endif() +endif() + +# Disable threading support for rlottie as this functionality actually leads to far worse # performance. As well there is a bug on Windows that makes rlottie hang forever on application # shutdown if compiled using MinGW with threading support enabled. option(LOTTIE_THREAD OFF) option(LOTTIE_MODULE OFF) -# Only use the compiler and linker flags defined by rlottie. -unset(CMAKE_CXX_FLAGS) -unset(CMAKE_EXE_LINKER_FLAGS) - if(EMSCRIPTEN) set(CMAKE_CXX_FLAGS -pthread) endif() +# rlottie generates a lot of annoying compiler warnings that we don't need to show. +if(CMAKE_CXX_COMPILER_ID MATCHES MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244 /wd4267 /wd4530 /wd4996") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") +endif() + add_subdirectory(rlottie EXCLUDE_FROM_ALL) From f7d72b4176aed904a076fc6c72cf1eeb1b5913fe Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 8 Oct 2022 11:12:45 +0200 Subject: [PATCH 07/56] Fixed an issue where rlottie was built as a shared library on Unix and macOS. --- external/CMakeLists.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index cbe8610c7..70a46be03 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -32,11 +32,12 @@ if(WIN32) # Strip the DLL files when building with MinGW. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") endif() - if (WIN32) - set(BUILD_SHARED_LIBS ON) - else() - set(BUILD_SHARED_LIBS OFF) - endif() +endif() + +if (WIN32) + set(BUILD_SHARED_LIBS ON) +else() + set(BUILD_SHARED_LIBS OFF) endif() # Disable threading support for rlottie as this functionality actually leads to far worse From fdcd4a4a9d3b336057d161abc7d1754d37db5685 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 8 Oct 2022 11:22:46 +0200 Subject: [PATCH 08/56] (Windows) Fixed an MSVC compiler warning. --- es-core/src/resources/Font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 2e54a6549..02140261a 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -308,7 +308,7 @@ std::string Font::wrapText(const std::string& text, size_t byteCount {0}; std::string wrappedText; std::string charEntry; - std::vector> dotsSection; + std::vector> dotsSection; bool addDots {false}; for (size_t i = 0; i < text.length(); ++i) { From 565c4a11870cc887d6e1c08102ff9a39c7d92878 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 9 Oct 2022 12:03:26 +0200 Subject: [PATCH 09/56] (slate-DE) Removed the unused blank.ttf font. --- themes/slate-DE/core/fonts/blank.ttf | Bin 72384 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 themes/slate-DE/core/fonts/blank.ttf diff --git a/themes/slate-DE/core/fonts/blank.ttf b/themes/slate-DE/core/fonts/blank.ttf deleted file mode 100644 index 899d4876ddda230405f12631ea9f41e6a5b2f74b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72384 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 From 50832a5b64879935c748fcd5e8471bdf0d4033f3 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 9 Oct 2022 17:59:27 +0200 Subject: [PATCH 10/56] Some code cleanup. --- es-core/src/components/ComponentList.cpp | 48 ++++++++++++------------ es-core/src/components/TextComponent.cpp | 4 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index 93c4cd326..dae7d2a45 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -134,7 +134,7 @@ void ComponentList::update(int deltaTime) mLoopTime = 0; } - const float totalHeight = getTotalRowHeight(); + const float totalHeight {getTotalRowHeight()}; // Scroll indicator logic, used by ScrollIndicatorComponent. bool scrollIndicatorChanged = false; @@ -236,17 +236,17 @@ void ComponentList::onCursorChanged(const CursorState& state) void ComponentList::updateCameraOffset() { - float oldCameraOffset = mCameraOffset; + float oldCameraOffset {mCameraOffset}; // Move the camera to scroll. - const float totalHeight = getTotalRowHeight(); + const float totalHeight {getTotalRowHeight()}; if (totalHeight > mSize.y) { - float target = - mSelectorBarOffset + getRowHeight(mEntries.at(mCursor).data) / 2.0f - (mSize.y / 2.0f); + float target {mSelectorBarOffset + getRowHeight(mEntries.at(mCursor).data) / 2.0f - + (mSize.y / 2.0f)}; // Clamp the camera to prevent a fraction of a row from being displayed. mCameraOffset = 0.0f; - unsigned int i = 0; + unsigned int i {0}; while (mCameraOffset < target && i < mEntries.size()) { mCameraOffset += getRowHeight(mEntries.at(i).data); if (mCameraOffset > totalHeight - mSize.y) { @@ -305,7 +305,7 @@ void ComponentList::render(const glm::mat4& parentTrans) // Draw our entries. std::vector drawAfterCursor; - bool drawAll; + bool drawAll {false}; for (size_t i = 0; i < mEntries.size(); ++i) { if (mLoopRows && mFocused && mLoopOffset > 0) { @@ -337,16 +337,16 @@ void ComponentList::render(const glm::mat4& parentTrans) if (mFocused && i == static_cast(mCursor) && it->component->getValue() != "") { // Check if we're dealing with text or an image component. - bool isTextComponent = true; - unsigned int origColor = it->component->getColor(); + bool isTextComponent {true}; + unsigned int origColor {it->component->getColor()}; if (origColor == 0) { origColor = it->component->getColorShift(); isTextComponent = false; } // Check if the color is neutral. - unsigned char byteRed = origColor >> 24 & 0xFF; - unsigned char byteGreen = origColor >> 16 & 0xFF; - unsigned char byteBlue = origColor >> 8 & 0xFF; + unsigned char byteRed {static_cast(origColor >> 24 & 0xFF)}; + unsigned char byteGreen {static_cast(origColor >> 16 & 0xFF)}; + unsigned char byteBlue {static_cast(origColor >> 8 & 0xFF)}; // If it's neutral, just proceed with normal rendering. if (byteRed == byteGreen && byteGreen == byteBlue) { renderLoopFunc(); @@ -379,7 +379,7 @@ void ComponentList::render(const glm::mat4& parentTrans) // Draw selector bar. if (mFocused) { - const float selectedRowHeight = getRowHeight(mEntries.at(mCursor).data); + const float selectedRowHeight {getRowHeight(mEntries.at(mCursor).data)}; if (mOpacity == 1.0f) { mRenderer->drawRect(0.0f, mSelectorBarOffset, std::round(mSize.x), selectedRowHeight, @@ -401,7 +401,7 @@ void ComponentList::render(const glm::mat4& parentTrans) } // Draw separators. - float y = 0; + float y {0.0f}; for (unsigned int i = 0; i < mEntries.size(); ++i) { mRenderer->drawRect(0.0f, y, std::round(mSize.x), 1.0f * Renderer::getScreenHeightModifier(), 0xC6C7C6FF, 0xC6C7C6FF, @@ -417,7 +417,7 @@ void ComponentList::render(const glm::mat4& parentTrans) float ComponentList::getRowHeight(const ComponentListRow& row) const { // Returns the highest component height found in the row. - float height = 0; + float height {0.0f}; for (unsigned int i = 0; i < row.elements.size(); ++i) { if (row.elements.at(i).component->getSize().y > height) height = row.elements.at(i).component->getSize().y; @@ -428,7 +428,7 @@ float ComponentList::getRowHeight(const ComponentListRow& row) const float ComponentList::getTotalRowHeight() const { - float height = 0; + float height {0.0f}; for (auto it = mEntries.cbegin(); it != mEntries.cend(); ++it) height += getRowHeight(it->data); @@ -437,13 +437,13 @@ float ComponentList::getTotalRowHeight() const void ComponentList::updateElementPosition(const ComponentListRow& row) { - float yOffset = 0; + float yOffset {0.0f}; for (auto it = mEntries.cbegin(); it != mEntries.cend() && &it->data != &row; ++it) yOffset += getRowHeight(it->data); // Assumes updateElementSize has already been called. - float rowHeight = getRowHeight(row); - float x = mHorizontalPadding / 2.0f; + float rowHeight {getRowHeight(row)}; + float x {mHorizontalPadding / 2.0f}; for (unsigned int i = 0; i < row.elements.size(); ++i) { const auto comp = row.elements.at(i).component; @@ -456,7 +456,7 @@ void ComponentList::updateElementPosition(const ComponentListRow& row) void ComponentList::updateElementSize(const ComponentListRow& row) { - float width = mSize.x - mHorizontalPadding; + float width {mSize.x - mHorizontalPadding}; std::vector> resizeVec; for (auto it = row.elements.cbegin(); it != row.elements.cend(); ++it) { @@ -485,11 +485,11 @@ std::vector ComponentList::getHelpPrompts() if (!size()) return std::vector(); - std::vector prompts = - mEntries.at(mCursor).data.elements.back().component->getHelpPrompts(); + std::vector prompts { + mEntries.at(mCursor).data.elements.back().component->getHelpPrompts()}; if (size() > 1) { - bool addMovePrompt = true; + bool addMovePrompt {true}; for (auto it = prompts.cbegin(); it != prompts.cend(); ++it) { if (it->first == "up/down" || it->first == "up/down/left/right") { addMovePrompt = false; @@ -505,7 +505,7 @@ std::vector ComponentList::getHelpPrompts() bool ComponentList::moveCursor(int amt) { - bool ret = listInput(amt); + bool ret {listInput(amt)}; listInput(0); return ret; } diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index 15c7130a5..f84a77aef 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -265,7 +265,7 @@ void TextComponent::onTextChanged() const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y > lineHeight}; const bool isScrollable {mParent && mParent->isScrollable()}; - if (isMultiline && text.size() && !isScrollable) { + if (isMultiline && !isScrollable) { const std::string wrappedText { font->wrapText(text, mSize.x, (mVerticalAutoSizing ? 0.0f : mSize.y - lineHeight), mLineSpacing, isMultiline)}; @@ -337,7 +337,7 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, componentName = "gamelistInfoComponent"; } - const ThemeData::ThemeElement* elem = theme->getElement(view, element, elementType); + const ThemeData::ThemeElement* elem {theme->getElement(view, element, elementType)}; if (!elem) return; From 0757156caff45d6fcc86ef43f8fb51664ee60e15 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 9 Oct 2022 18:01:30 +0200 Subject: [PATCH 11/56] Improved error handling for unloadable font files. --- es-core/src/resources/Font.cpp | 22 ++++++++++------------ es-core/src/resources/Font.h | 8 ++++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 02140261a..d19d57bde 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -4,7 +4,7 @@ // Font.h // // Loading, unloading, caching and rendering of fonts. -// Also functions for word wrapping and similar. +// Also functions for text wrapping and similar. // #include "resources/Font.h" @@ -12,12 +12,9 @@ #include "Log.h" #include "renderers/Renderer.h" #include "utils/FileSystemUtil.h" +#include "utils/PlatformUtil.h" #include "utils/StringUtil.h" -FT_Library Font::sLibrary {nullptr}; - -std::map, std::weak_ptr> Font::sFontMap; - Font::Font(int size, const std::string& path) : mRenderer {Renderer::getInstance()} , mFontSize(size) @@ -607,15 +604,15 @@ void Font::FontTexture::deinitTexture() } } -Font::FontFace::FontFace(ResourceData&& d, int size) +Font::FontFace::FontFace(ResourceData&& d, int size, const std::string& path) : data {d} { - int err { - FT_New_Memory_Face(sLibrary, data.ptr.get(), static_cast(data.length), 0, &face)}; - assert(!err); + if (FT_New_Memory_Face(sLibrary, d.ptr.get(), static_cast(d.length), 0, &face) != 0) { + LOG(LogError) << "Couldn't load font file \"" << path << "\""; + Utils::Platform::emergencyShutdown(); + } - if (!err) - FT_Set_Pixel_Sizes(face, 0, size); + FT_Set_Pixel_Sizes(face, 0, size); } Font::FontFace::~FontFace() @@ -708,7 +705,8 @@ FT_Face Font::getFaceForChar(unsigned int id) // Otherwise, take from fallbackFonts. const std::string& path {i == 0 ? mPath : fallbackFonts.at(i - 1)}; ResourceData data {ResourceManager::getInstance().getFileData(path)}; - mFaceCache[i] = std::unique_ptr(new FontFace(std::move(data), mFontSize)); + mFaceCache[i] = + std::unique_ptr(new FontFace(std::move(data), mFontSize, mPath)); fit = mFaceCache.find(i); } diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index ef6b11302..602c763a5 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -4,7 +4,7 @@ // Font.h // // Loading, unloading, caching and rendering of fonts. -// Also functions for word wrapping and similar. +// Also functions for text wrapping and similar. // #ifndef ES_CORE_RESOURCES_FONT_H @@ -105,8 +105,8 @@ public: private: Renderer* mRenderer; - static FT_Library sLibrary; - static std::map, std::weak_ptr> sFontMap; + static inline FT_Library sLibrary {nullptr}; + static inline std::map, std::weak_ptr> sFontMap; Font(int size, const std::string& path); @@ -135,7 +135,7 @@ private: const ResourceData data; FT_Face face; - FontFace(ResourceData&& d, int size); + FontFace(ResourceData&& d, int size, const std::string& path); virtual ~FontFace(); }; From 0232635504991e2ea21c43aadd8b4113e7b14881 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 9 Oct 2022 19:13:54 +0200 Subject: [PATCH 12/56] Refactoring/cleanup. --- es-core/src/resources/Font.cpp | 99 ++++++++++++++-------------------- es-core/src/resources/Font.h | 64 ++++++++++------------ 2 files changed, 69 insertions(+), 94 deletions(-) diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index d19d57bde..c89f4dafc 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -17,10 +17,10 @@ Font::Font(int size, const std::string& path) : mRenderer {Renderer::getInstance()} + , mPath(path) + , mTextSize {0.0f, 0.0f} , mFontSize(size) , mMaxGlyphHeight {0} - , mTextSize {0.0f, 0.0f} - , mPath(path) { if (mFontSize < 9) { mFontSize = 9; @@ -56,45 +56,6 @@ Font::~Font() } } -void Font::initLibrary() -{ - assert(sLibrary == nullptr); - - if (FT_Init_FreeType(&sLibrary)) { - sLibrary = nullptr; - LOG(LogError) << "Couldn't initialize FreeType"; - } -} - -std::vector Font::getFallbackFontPaths() -{ - std::vector fontPaths; - - // Standard fonts, let's include them here for exception handling purposes even though that's - // not really the correct location. (The application will crash if they are missing.) - ResourceManager::getInstance().getResourcePath(":/fonts/Akrobat-Regular.ttf"); - ResourceManager::getInstance().getResourcePath(":/fonts/Akrobat-SemiBold.ttf"); - ResourceManager::getInstance().getResourcePath(":/fonts/Akrobat-Bold.ttf"); - - // Vera sans Unicode. - fontPaths.push_back(ResourceManager::getInstance().getResourcePath(":/fonts/DejaVuSans.ttf")); - // GNU FreeFont monospaced. - fontPaths.push_back(ResourceManager::getInstance().getResourcePath(":/fonts/FreeMono.ttf")); - // Various languages, such as Japanese and Chinese. - fontPaths.push_back( - ResourceManager::getInstance().getResourcePath(":/fonts/DroidSansFallbackFull.ttf")); - // Korean. - fontPaths.push_back( - ResourceManager::getInstance().getResourcePath(":/fonts/NanumMyeongjo.ttf")); - // Font Awesome icon glyphs, used for various special symbols like stars, folders etc. - fontPaths.push_back( - ResourceManager::getInstance().getResourcePath(":/fonts/fontawesome-webfont.ttf")); - // This is only needed for some really rare special characters. - fontPaths.push_back(ResourceManager::getInstance().getResourcePath(":/fonts/Ubuntu-C.ttf")); - - return fontPaths; -} - std::shared_ptr Font::get(int size, const std::string& path) { const std::string canonicalPath {Utils::FileSystem::getCanonicalPath(path)}; @@ -107,7 +68,7 @@ std::shared_ptr Font::get(int size, const std::string& path) return foundFont->second.lock(); } - std::shared_ptr font {std::shared_ptr(new Font(def.second, def.first))}; + std::shared_ptr font {new Font(def.second, def.first)}; sFontMap[def] = std::weak_ptr(font); ResourceManager::getInstance().addReloadable(font); return font; @@ -145,16 +106,6 @@ glm::vec2 Font::sizeText(std::string text, float lineSpacing) return glm::vec2 {highestWidth, y}; } -std::string Font::getTextMaxWidth(std::string text, float maxWidth) -{ - float width {sizeText(text).x}; - while (width > maxWidth) { - text.pop_back(); - width = sizeText(text).x; - } - return text; -} - TextCache* Font::buildTextCache(const std::string& text, float offsetX, float offsetY, @@ -442,12 +393,6 @@ glm::vec2 Font::getWrappedTextCursorOffset(const std::string& wrappedText, return glm::vec2 {lineWidth, yPos}; } -float Font::getHeight(float lineSpacing) const -{ - // Return overall height including line spacing. - return mMaxGlyphHeight * lineSpacing; -} - float Font::getLetterHeight() { Glyph* glyph {getGlyph('S')}; @@ -519,6 +464,34 @@ size_t Font::getTotalMemUsage() return total; } +std::vector Font::getFallbackFontPaths() +{ + std::vector fontPaths; + + // Default application fonts. + ResourceManager::getInstance().getResourcePath(":/fonts/Akrobat-Regular.ttf"); + ResourceManager::getInstance().getResourcePath(":/fonts/Akrobat-SemiBold.ttf"); + ResourceManager::getInstance().getResourcePath(":/fonts/Akrobat-Bold.ttf"); + + // Vera sans Unicode. + fontPaths.push_back(ResourceManager::getInstance().getResourcePath(":/fonts/DejaVuSans.ttf")); + // GNU FreeFont monospaced. + fontPaths.push_back(ResourceManager::getInstance().getResourcePath(":/fonts/FreeMono.ttf")); + // Various languages, such as Japanese and Chinese. + fontPaths.push_back( + ResourceManager::getInstance().getResourcePath(":/fonts/DroidSansFallbackFull.ttf")); + // Korean. + fontPaths.push_back( + ResourceManager::getInstance().getResourcePath(":/fonts/NanumMyeongjo.ttf")); + // Font Awesome icon glyphs, used for various special symbols like stars, folders etc. + fontPaths.push_back( + ResourceManager::getInstance().getResourcePath(":/fonts/fontawesome-webfont.ttf")); + // This is only needed for some really rare special characters. + fontPaths.push_back(ResourceManager::getInstance().getResourcePath(":/fonts/Ubuntu-C.ttf")); + + return fontPaths; +} + Font::FontTexture::FontTexture(const int mFontSize) { textureId = 0; @@ -621,6 +594,16 @@ Font::FontFace::~FontFace() FT_Done_Face(face); } +void Font::initLibrary() +{ + assert(sLibrary == nullptr); + + if (FT_Init_FreeType(&sLibrary)) { + sLibrary = nullptr; + LOG(LogError) << "Couldn't initialize FreeType"; + } +} + void Font::rebuildTextures() { // Recreate OpenGL textures. diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index 602c763a5..f2f4ea09d 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -42,16 +42,11 @@ class Font : public IReloadable { public: virtual ~Font(); - static void initLibrary(); - std::vector getFallbackFontPaths(); static std::shared_ptr get(int size, const std::string& path = getDefaultPath()); // Returns the expected size of a string when rendered. Extra spacing is applied to the Y axis. glm::vec2 sizeText(std::string text, float lineSpacing = 1.5f); - // Returns the portion of a string that fits within the passed argument maxWidth. - std::string getTextMaxWidth(std::string text, float maxWidth); - // Returns the size of the overall text area. const glm::vec2 getTextSize() { return mTextSize; } @@ -84,7 +79,8 @@ public: const size_t stop, const float lineSpacing = 1.5f); - float getHeight(float lineSpacing = 1.5f) const; + // Return overall height including line spacing. + float getHeight(float lineSpacing = 1.5f) const { return mMaxGlyphHeight * lineSpacing; } float getLetterHeight(); void reload(ResourceManager& rm) override { rebuildTextures(); } @@ -104,16 +100,12 @@ public: static size_t getTotalMemUsage(); private: - Renderer* mRenderer; - static inline FT_Library sLibrary {nullptr}; - static inline std::map, std::weak_ptr> sFontMap; - Font(int size, const std::string& path); + static void initLibrary(); struct FontTexture { unsigned int textureId; glm::ivec2 textureSize; - glm::ivec2 writePos; int rowHeight; @@ -126,8 +118,7 @@ private: // updating textureId. void initTexture(); - // Deinitializes the OpenGL texture if any exists, is automatically called - // in the destructor. + // Deinitializes any existing OpenGL textures, is automatically called in destructor. void deinitTexture(); }; @@ -139,6 +130,14 @@ private: virtual ~FontFace(); }; + struct Glyph { + FontTexture* texture; + glm::vec2 texPos; + glm::vec2 texSize; // In texels. + glm::vec2 advance; + glm::vec2 bearing; + }; + // Completely recreate the texture data for all textures based on mGlyphs information. void rebuildTextures(); void unloadTextures(); @@ -147,35 +146,30 @@ private: FontTexture*& tex_out, glm::ivec2& cursor_out); - std::map> mFaceCache; + std::vector getFallbackFontPaths(); FT_Face getFaceForChar(unsigned int id); - void clearFaceCache() { mFaceCache.clear(); } - - struct Glyph { - FontTexture* texture; - - glm::vec2 texPos; - glm::vec2 texSize; // In texels. - - glm::vec2 advance; - glm::vec2 bearing; - }; - - std::vector mTextures; - std::map mGlyphMap; Glyph* getGlyph(const unsigned int id); - int mFontSize; - int mMaxGlyphHeight; - glm::vec2 mTextSize; - const std::string mPath; - float getNewlineStartOffset(const std::string& text, const unsigned int& charStart, const float& xLen, const Alignment& alignment); - friend TextCache; + void clearFaceCache() { mFaceCache.clear(); } + + static inline FT_Library sLibrary {nullptr}; + static inline std::map, std::weak_ptr> sFontMap; + static inline std::vector mFallbackFonts; + + Renderer* mRenderer; + std::map> mFaceCache; + std::vector mTextures; + std::map mGlyphMap; + + const std::string mPath; + glm::vec2 mTextSize; + int mFontSize; + int mMaxGlyphHeight; }; // Used to store a sort of "pre-rendered" string. @@ -200,8 +194,6 @@ public: protected: struct VertexList { std::vector verts; - // This is a pointer because the texture ID can change during - // deinit/reinit (when launching a game). unsigned int* textureIdPtr; }; From 6af1f76be8d3926c8c067f9df788b46f3ab82313 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 10 Oct 2022 20:07:40 +0200 Subject: [PATCH 13/56] Fixed an issue where the help system was rendered on top of the menus. --- es-core/src/Window.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index df00cbcee..b79920ec8 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -541,6 +541,9 @@ void Window::render() } } + if (!mRenderedHelpPrompts) + mHelp->render(trans); + if (!mRenderLaunchScreen) top->render(trans); } @@ -568,9 +571,6 @@ void Window::render() delete cache; } - if (!mRenderedHelpPrompts) - mHelp->render(trans); - unsigned int screensaverTimer = static_cast(Settings::getInstance()->getInt("ScreensaverTimer")); if (mTimeSinceLastInput >= screensaverTimer && screensaverTimer != 0) { From 4cedd9119f399e425ffd8fe23d2230df18c9ece3 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 10 Oct 2022 20:09:53 +0200 Subject: [PATCH 14/56] Fixed some help icons that were inadvertently set as slightly transparent. Also removed some obsolete help graphics files. --- resources/graphics/help/button_1.svg | 5 --- resources/graphics/help/button_2.svg | 5 --- resources/graphics/help/button_3.svg | 5 --- resources/graphics/help/button_4.svg | 5 --- .../graphics/help/button_back_XBOX360.svg | 41 ++++--------------- resources/graphics/help/button_start_PS5.svg | 41 ++++--------------- resources/graphics/help/button_start_XBOX.svg | 41 ++++--------------- .../graphics/help/button_start_XBOX360.svg | 38 +++-------------- 8 files changed, 27 insertions(+), 154 deletions(-) delete mode 100644 resources/graphics/help/button_1.svg delete mode 100644 resources/graphics/help/button_2.svg delete mode 100644 resources/graphics/help/button_3.svg delete mode 100644 resources/graphics/help/button_4.svg diff --git a/resources/graphics/help/button_1.svg b/resources/graphics/help/button_1.svg deleted file mode 100644 index aea898fe4..000000000 --- a/resources/graphics/help/button_1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/resources/graphics/help/button_2.svg b/resources/graphics/help/button_2.svg deleted file mode 100644 index 4216cb991..000000000 --- a/resources/graphics/help/button_2.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/resources/graphics/help/button_3.svg b/resources/graphics/help/button_3.svg deleted file mode 100644 index 2c2f21f89..000000000 --- a/resources/graphics/help/button_3.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/resources/graphics/help/button_4.svg b/resources/graphics/help/button_4.svg deleted file mode 100644 index dc31c1711..000000000 --- a/resources/graphics/help/button_4.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/resources/graphics/help/button_back_XBOX360.svg b/resources/graphics/help/button_back_XBOX360.svg index 1b1e097ab..59a25fb07 100644 --- a/resources/graphics/help/button_back_XBOX360.svg +++ b/resources/graphics/help/button_back_XBOX360.svg @@ -1,19 +1,15 @@ + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -22,36 +18,13 @@ image/svg+xml - - + id="rect4801" /> diff --git a/resources/graphics/help/button_start_PS5.svg b/resources/graphics/help/button_start_PS5.svg index cb49a9607..f304b1354 100644 --- a/resources/graphics/help/button_start_PS5.svg +++ b/resources/graphics/help/button_start_PS5.svg @@ -1,19 +1,15 @@ + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -22,36 +18,13 @@ image/svg+xml - - + id="path4749" /> diff --git a/resources/graphics/help/button_start_XBOX.svg b/resources/graphics/help/button_start_XBOX.svg index 87c0a74f4..f304b1354 100644 --- a/resources/graphics/help/button_start_XBOX.svg +++ b/resources/graphics/help/button_start_XBOX.svg @@ -1,19 +1,15 @@ + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -22,36 +18,13 @@ image/svg+xml - - + id="path4749" /> diff --git a/resources/graphics/help/button_start_XBOX360.svg b/resources/graphics/help/button_start_XBOX360.svg index 656deb9d2..feda63314 100644 --- a/resources/graphics/help/button_start_XBOX360.svg +++ b/resources/graphics/help/button_start_XBOX360.svg @@ -1,19 +1,15 @@ + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -22,35 +18,13 @@ image/svg+xml - - From 610ac9adb3ea929ac5e95ed0dba7e79a975bc623 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 10 Oct 2022 20:32:35 +0200 Subject: [PATCH 15/56] Implemented dynamic texture allocation to the font handling. --- es-core/src/resources/Font.cpp | 138 +++++++++++++-------------------- es-core/src/resources/Font.h | 6 +- 2 files changed, 55 insertions(+), 89 deletions(-) diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index c89f4dafc..a4de6521f 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -22,12 +22,12 @@ Font::Font(int size, const std::string& path) , mFontSize(size) , mMaxGlyphHeight {0} { - if (mFontSize < 9) { - mFontSize = 9; + if (mFontSize < 3) { + mFontSize = 3; LOG(LogWarning) << "Requested font size too small, changing to minimum supported size"; } - else if (mFontSize > Renderer::getScreenHeight()) { - mFontSize = static_cast(Renderer::getScreenHeight()); + else if (mFontSize > Renderer::getScreenHeight() * 1.5f) { + mFontSize = static_cast(Renderer::getScreenHeight() * 1.5f); LOG(LogWarning) << "Requested font size too large, changing to maximum supported size"; } @@ -205,12 +205,12 @@ TextCache* Font::buildTextCache(const std::string& text, cache->vertexLists.resize(vertMap.size()); cache->metrics = {sizeText(text, lineSpacing)}; - unsigned int i {0}; + size_t i {0}; for (auto it = vertMap.cbegin(); it != vertMap.cend(); ++it) { TextCache::VertexList& vertList = cache->vertexLists.at(i); - vertList.textureIdPtr = &it->first->textureId; vertList.verts = it->second; + ++i; } clearFaceCache(); @@ -402,23 +402,24 @@ float Font::getLetterHeight() std::shared_ptr Font::getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, - const std::shared_ptr& orig) + const std::shared_ptr& orig, + const float maxHeight) { using namespace ThemeFlags; if (!(properties & FONT_PATH) && !(properties & FONT_SIZE)) return orig; - std::shared_ptr font; int size {static_cast(orig ? orig->mFontSize : FONT_SIZE_MEDIUM)}; std::string path {orig ? orig->mPath : getDefaultPath()}; - float sh {static_cast(Renderer::getScreenHeight())}; + float screenHeight {static_cast(Renderer::getScreenHeight())}; - // Make sure the size is not unreasonably large (which may be caused by a mistake in the - // theme configuration). if (properties & FONT_SIZE && elem->has("fontSize")) - size = glm::clamp(static_cast(sh * elem->get("fontSize")), 0, - static_cast(Renderer::getInstance()->getScreenHeight())); + size = static_cast(glm::clamp(screenHeight * elem->get("fontSize"), + screenHeight * 0.001f, screenHeight * 1.5f)); + + if (maxHeight != 0.0f && static_cast(size) > maxHeight) + size = static_cast(maxHeight); if (properties & FONT_PATH && elem->has("fontPath")) path = elem->get("fontPath"); @@ -438,7 +439,7 @@ size_t Font::getMemUsage() const { size_t memUsage {0}; for (auto it = mTextures.cbegin(); it != mTextures.cend(); ++it) - memUsage += it->textureSize.x * it->textureSize.y * 4; + memUsage += (*it)->textureSize.x * (*it)->textureSize.y * 4; for (auto it = mFaceCache.cbegin(); it != mFaceCache.cend(); ++it) memUsage += it->second->data.length; @@ -495,36 +496,12 @@ std::vector Font::getFallbackFontPaths() Font::FontTexture::FontTexture(const int mFontSize) { textureId = 0; - - // This is a hack to add some extra texture size when running at very low resolutions. If not - // doing this, the use of fallback fonts (such as Japanese characters) could result in the - // texture not fitting the glyphs. - int extraTextureSize {0}; - const float screenSizeModifier { - std::min(Renderer::getScreenWidthModifier(), Renderer::getScreenHeightModifier())}; - - if (screenSizeModifier < 0.2f) - extraTextureSize += 6; - if (screenSizeModifier < 0.45f) - extraTextureSize += 4; - - // It's not entirely clear if the 20 and 22 constants are correct, but they seem to provide - // a texture buffer large enough to hold the fonts. This logic is obviously a hack though - // and needs to be properly reviewed and improved. - textureSize = - glm::ivec2 {mFontSize * (20 + extraTextureSize), mFontSize * (22 + extraTextureSize / 2)}; - - // Make sure the size is not unreasonably large (which may be caused by a mistake in the - // theme configuration). - if (textureSize.x > static_cast(Renderer::getScreenWidth()) * 10) - textureSize.x = - glm::clamp(textureSize.x, 0, static_cast(Renderer::getScreenWidth()) * 10); - if (textureSize.y > static_cast(Renderer::getScreenHeight()) * 10) - textureSize.y = - glm::clamp(textureSize.y, 0, static_cast(Renderer::getScreenHeight()) * 10); - - writePos = glm::ivec2 {0, 0}; rowHeight = 0; + writePos = glm::ivec2 {0, 0}; + + // Set the texture to a reasonable size, if we run out of space for adding glyphs then + // more textures will be created dynamically. + textureSize = glm::ivec2 {mFontSize * 6, mFontSize * 6}; } Font::FontTexture::~FontTexture() @@ -540,16 +517,14 @@ bool Font::FontTexture::findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out if (writePos.x + size.x >= textureSize.x && writePos.y + rowHeight + size.y + 1 < textureSize.y) { - // Row full, but it should fit on the next row so move the cursor there. + // Row is full, but the glyph should fit on the next row so move the cursor there. // Leave 1px of space between glyphs. writePos = glm::ivec2 {0, writePos.y + rowHeight + 1}; rowHeight = 0; } - if (writePos.x + size.x >= textureSize.x || writePos.y + size.y >= textureSize.y) { - // Nope, still won't fit. - return false; - } + if (writePos.x + size.x >= textureSize.x || writePos.y + size.y >= textureSize.y) + return false; // No it still won't fit. cursor_out = writePos; // Leave 1px of space between glyphs. @@ -608,7 +583,7 @@ void Font::rebuildTextures() { // Recreate OpenGL textures. for (auto it = mTextures.begin(); it != mTextures.end(); ++it) - it->initTexture(); + (*it)->initTexture(); // Re-upload the texture data. for (auto it = mGlyphMap.cbegin(); it != mGlyphMap.cend(); ++it) { @@ -635,7 +610,7 @@ void Font::rebuildTextures() void Font::unloadTextures() { for (auto it = mTextures.begin(); it != mTextures.end(); ++it) - it->deinitTexture(); + (*it)->deinitTexture(); } void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize, @@ -643,29 +618,19 @@ void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize, glm::ivec2& cursor_out) { if (mTextures.size()) { - // Check if the most recent texture has space. - tex_out = &mTextures.back(); + // Check if the most recent texture has space available for the glyph. + tex_out = mTextures.back().get(); // Will this one work? if (tex_out->findEmpty(glyphSize, cursor_out)) return; // Yes. } - // This should never happen, assuming the texture size is large enough to fit the font, - // as set in the FontTexture constructor. In the unlikely situation that it still happens, - // setting the texture to nullptr makes sure the application doesn't crash and that the - // user is clearly notified of the problem by the fact that the glyph/character will be - // completely missing. - if (mGlyphMap.size() > 0) { - tex_out = nullptr; - return; - } - - mTextures.push_back(FontTexture(mFontSize)); - tex_out = &mTextures.back(); + mTextures.emplace_back(std::make_unique(mFontSize)); + tex_out = mTextures.back().get(); tex_out->initTexture(); - bool ok = tex_out->findEmpty(glyphSize, cursor_out); + bool ok {tex_out->findEmpty(glyphSize, cursor_out)}; if (!ok) { LOG(LogError) << "Glyph too big to fit on a new texture (glyph size > " << tex_out->textureSize.x << ", " << tex_out->textureSize.y << ")"; @@ -677,15 +642,11 @@ FT_Face Font::getFaceForChar(unsigned int id) { static const std::vector fallbackFonts {getFallbackFontPaths()}; - // Look through our current font + fallback fonts to see if any have the - // glyph we're looking for. + // Look for the glyph in our current font and then in the fallback fonts if needed. for (unsigned int i = 0; i < fallbackFonts.size() + 1; ++i) { auto fit = mFaceCache.find(i); - // Doesn't exist yet. if (fit == mFaceCache.cend()) { - // i == 0 -> mPath - // Otherwise, take from fallbackFonts. const std::string& path {i == 0 ? mPath : fallbackFonts.at(i - 1)}; ResourceData data {ResourceManager::getInstance().getFileData(path)}; mFaceCache[i] = @@ -697,18 +658,18 @@ FT_Face Font::getFaceForChar(unsigned int id) return fit->second->face; } - // Nothing has a valid glyph - return the "real" face so we get a "missing" character. + // Couldn't find a valid glyph, return the "real" face so we get a "missing" character. return mFaceCache.cbegin()->second->face; } Font::Glyph* Font::getGlyph(const unsigned int id) { - // Is it already loaded? + // Check if the glyph has already been loaded. auto it = mGlyphMap.find(id); if (it != mGlyphMap.cend()) return &it->second; - // Nope, need to make a glyph. + // We need to create a new entry. FT_Face face {getFaceForChar(id)}; if (!face) { LOG(LogError) << "Couldn't find appropriate font face for character " << id << " for font " @@ -716,22 +677,29 @@ Font::Glyph* Font::getGlyph(const unsigned int id) return nullptr; } - FT_GlyphSlot g {face->glyph}; + const FT_GlyphSlot glyphSlot {face->glyph}; - if (FT_Load_Char(face, id, FT_LOAD_RENDER)) { + // TODO: Evaluate/test hinting when HarfBuzz has been added. + // If the font does not contain hinting information then force the use of the automatic + // hinter that is built into FreeType. + // const bool hasHinting {static_cast(glyphSlot->face->face_flags & FT_FACE_FLAG_HINTER)}; + const bool hasHinting {true}; + + if (FT_Load_Char(face, id, + (hasHinting ? + FT_LOAD_RENDER : + FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT))) { LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath << ", size " << mFontSize; return nullptr; } - glm::ivec2 glyphSize {g->bitmap.width, g->bitmap.rows}; - FontTexture* tex {nullptr}; glm::ivec2 cursor {0, 0}; + const glm::ivec2 glyphSize {glyphSlot->bitmap.width, glyphSlot->bitmap.rows}; getTextureForNewGlyph(glyphSize, tex, cursor); - // getTextureForNewGlyph can fail if the glyph is bigger than the max texture - // size (absurdly large font size). + // This should (hopefully) never occur as size constraints are enforced earlier on. if (tex == nullptr) { LOG(LogError) << "Couldn't create glyph for character " << id << " for font " << mPath << ", size " << mFontSize << " (no suitable texture found)"; @@ -747,20 +715,18 @@ Font::Glyph* Font::getGlyph(const unsigned int id) glyph.texSize = glm::vec2 {glyphSize.x / static_cast(tex->textureSize.x), glyphSize.y / static_cast(tex->textureSize.y)}; - glyph.advance = glm::vec2 {static_cast(g->metrics.horiAdvance) / 64.0f, - static_cast(g->metrics.vertAdvance) / 64.0f}; - glyph.bearing = glm::vec2 {static_cast(g->metrics.horiBearingX) / 64.0f, - static_cast(g->metrics.horiBearingY) / 64.0f}; + glyph.advance = glm::vec2 {static_cast(glyphSlot->metrics.horiAdvance) / 64.0f, + static_cast(glyphSlot->metrics.vertAdvance) / 64.0f}; + glyph.bearing = glm::vec2 {static_cast(glyphSlot->metrics.horiBearingX) / 64.0f, + static_cast(glyphSlot->metrics.horiBearingY) / 64.0f}; // Upload glyph bitmap to texture. mRenderer->updateTexture(tex->textureId, Renderer::TextureType::RED, cursor.x, cursor.y, - glyphSize.x, glyphSize.y, g->bitmap.buffer); + glyphSize.x, glyphSize.y, glyphSlot->bitmap.buffer); - // Update max glyph height. if (glyphSize.y > mMaxGlyphHeight) mMaxGlyphHeight = glyphSize.y; - // Done. return &glyph; } diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index f2f4ea09d..df2b5d506 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -92,7 +92,8 @@ public: static std::shared_ptr getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, - const std::shared_ptr& orig); + const std::shared_ptr& orig, + const float maxHeight = 0.0f); // Returns an approximation of VRAM used by this font's texture (in bytes). size_t getMemUsage() const; @@ -159,11 +160,10 @@ private: static inline FT_Library sLibrary {nullptr}; static inline std::map, std::weak_ptr> sFontMap; - static inline std::vector mFallbackFonts; Renderer* mRenderer; + std::vector> mTextures; std::map> mFaceCache; - std::vector mTextures; std::map mGlyphMap; const std::string mPath; From 39c9bd2cbc75a0761711261dce1e7f7ddde2eb4d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 10 Oct 2022 20:37:04 +0200 Subject: [PATCH 16/56] Added font size overflow restrictions to TextComponent and DateTimeComponent. Also fixed a crash that could occur in TextComponent when blank/dummy fonts were used. --- es-core/src/components/DateTimeComponent.cpp | 10 +++++++++- es-core/src/components/TextComponent.cpp | 18 +++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index 08a0ae052..faa2537ce 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -209,6 +209,14 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, } } + float maxHeight {0.0f}; + + if (!theme->isLegacyTheme() && properties & elem->has("size")) { + const glm::vec2 size {elem->get("size")}; + if (size.x != 0.0f && size.y != 0.0f) + maxHeight = mSize.y * 2.0f; + } + // Legacy themes only. if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) setUppercase(elem->get("forceUppercase")); @@ -216,5 +224,5 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, if (properties & LINE_SPACING && elem->has("lineSpacing")) setLineSpacing(glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f)); - setFont(Font::getFromTheme(elem, properties, mFont)); + setFont(Font::getFromTheme(elem, properties, mFont, maxHeight)); } diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index f84a77aef..2c13226cc 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -69,7 +69,7 @@ TextComponent::TextComponent(const std::string& text, void TextComponent::onSizeChanged() { - mAutoCalcExtent = glm::ivec2 {(getSize().x == 0), (getSize().y == 0)}; + mAutoCalcExtent = glm::ivec2 {getSize().x == 0, getSize().y == 0}; onTextChanged(); } @@ -254,8 +254,12 @@ void TextComponent::onTextChanged() text = mText; // Original case. } - if (mFont && mAutoCalcExtent.x) + if (mFont && mAutoCalcExtent.x) { mSize = mFont->sizeText(text, mLineSpacing); + // This can happen under special circumstances like when a blank/dummy font is used. + if (mSize.x == 0.0f) + return; + } if (!mFont || text.empty() || mSize.x < 0.0f) return; @@ -453,6 +457,14 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, } } + float maxHeight {0.0f}; + + if (!theme->isLegacyTheme() && properties & elem->has("size")) { + const glm::vec2 size {elem->get("size")}; + if (size.x != 0.0f && size.y != 0.0f) + maxHeight = mSize.y * 2.0f; + } + // Legacy themes only. if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) setUppercase(elem->get("forceUppercase")); @@ -460,5 +472,5 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, if (properties & LINE_SPACING && elem->has("lineSpacing")) setLineSpacing(glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f)); - setFont(Font::getFromTheme(elem, properties, mFont)); + setFont(Font::getFromTheme(elem, properties, mFont, maxHeight)); } From 1c82228a9c8dca73de60132ef19f8704b6ab6584 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 10 Oct 2022 20:52:55 +0200 Subject: [PATCH 17/56] Fixed two small logical errors. --- es-core/src/components/DateTimeComponent.cpp | 2 +- es-core/src/components/TextComponent.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index faa2537ce..d9bfb6de6 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -211,7 +211,7 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, float maxHeight {0.0f}; - if (!theme->isLegacyTheme() && properties & elem->has("size")) { + if (!theme->isLegacyTheme() && elem->has("size")) { const glm::vec2 size {elem->get("size")}; if (size.x != 0.0f && size.y != 0.0f) maxHeight = mSize.y * 2.0f; diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index 2c13226cc..f0449230d 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -459,7 +459,7 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, float maxHeight {0.0f}; - if (!theme->isLegacyTheme() && properties & elem->has("size")) { + if (!theme->isLegacyTheme() && elem->has("size")) { const glm::vec2 size {elem->get("size")}; if (size.x != 0.0f && size.y != 0.0f) maxHeight = mSize.y * 2.0f; From 241a0119ef8468a78e78645ba0434455aacb4a9e Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 10 Oct 2022 21:12:49 +0200 Subject: [PATCH 18/56] Fixed a crash in the Font::wrapText function. --- es-core/src/resources/Font.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index a4de6521f..28cfb1fef 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -347,21 +347,22 @@ std::string Font::wrapText(const std::string& text, } if (addDots) { - if (wrappedText.back() == ' ') { + if (!wrappedText.empty() && wrappedText.back() == ' ') { lineWidth -= sizeText(" ").x; wrappedText.pop_back(); } - else if (wrappedText.back() == '\t') { + else if (!wrappedText.empty() && wrappedText.back() == '\t') { lineWidth -= sizeText("\t").x; wrappedText.pop_back(); } - while (!dotsSection.empty() && lineWidth + dotsWidth > maxLength) { + while (!wrappedText.empty() && !dotsSection.empty() && lineWidth + dotsWidth > maxLength) { lineWidth -= dotsSection.back().second; wrappedText.erase(wrappedText.length() - dotsSection.back().first); dotsSection.pop_back(); } if (!wrappedText.empty() && wrappedText.back() == ' ') wrappedText.pop_back(); + wrappedText.append("..."); } From add8e376870b6519a486790d8a39033965fb1c0c Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 10 Oct 2022 21:37:39 +0200 Subject: [PATCH 19/56] Documentation update. --- CHANGELOG.md | 4 ++++ CONTRIBUTING.md | 4 +++- THEMES-DEV.md | 19 ++++++++++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9720be36c..5b1493f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,8 @@ * OpenGL ES: Added an OpenGLVersion setting for choosing between OpenGL ES 3.0, 3.1 and 3.2 (has to be manually set in es_settings.xml) * Greatly improved the performance of shader post-processing such as scanlines and blur rendering * Greatly improved application startup speed by avoiding a lot of unnecessary SVG rasterizations +* Implemented dynamic texture allocation to the font code to reduce memory usage and avoid missing glyphs +* Large optimizations to the text wrapping code (generallly 300-400% faster) * Added support for texture mipmapping with trilinear filtering * Added on-demand texture loading to the carousel * Improved the renderer scaling accuracy @@ -179,6 +181,7 @@ * Multiple levels of symlinking in the ROMs directory tree could crash the application on startup * During some menu operations that reloaded the gamelist view, the cached background could miss some components as they were not rendered in time +* Text wrapping did not work correctly for text that typically does not contain spaces, like Japanese * Changing some values using the metadata editor could lead to an incorrect sort order if the changes were done from within a grouped custom collection * Changing the setting "Group unthemed custom collections" could lead to incorrect custom collections sorting under some circumstances * Games located in subdirectories were not added back to custom collections when disabling the "Exclude from game counter" metadata option @@ -203,6 +206,7 @@ * ScrollableContainer faded semi-transparent text to fully opaque when resetting * ScrollableContainer faded in the background text color in addition to the text color when resetting * Text elements that had an opacity set to lower than FF via the color tag were faded in during gamelist scrolling +* The help system was rendered on top of menus if placed at such a location on the screen * Theme sets were not always sorted correctly (as seen when mixing uppercase and lowercase letters in theme names) * The SliderComponent knob was not consistently positioned * The device text flickered in GuiDetectDevice when configuring a controller diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92794cb31..f529d9583 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,7 @@ The roadmap is under constant review so expect it to change from time to time. S * Lottie animation (vector graphics) and GIF animation support * OpenGL ES 3.0 renderer for use on the Raspberry Pi * Replace the OpenGL fixed function pipeline with a shader-based renderer +* Improve text and font functions, e.g. dynamic texture allocation and faster and cleaner text wrapping * Improve the performance of the GLSL shader post-processing #### v2.1 @@ -92,7 +93,8 @@ The roadmap is under constant review so expect it to change from time to time. S * Proper audio mixer * Checksum support for the scraper for exact searches and for determining when to overwrite files * Support for portrait orientation, e.g. for Tate Mode arcade cabinets -* Improved text and font functions, e.g. faster and cleaner line wrapping and more exact sizing +* Replace the built-in Unicode functions and lookup tables with those of the ICU library +* Add text kerning support using the HarfBuzz library #### v2.3 diff --git a/THEMES-DEV.md b/THEMES-DEV.md index f11ceab09..d75c2b5eb 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -1209,7 +1209,9 @@ Properties: * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value can get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. + - Default is `0.045` * `horizontalAlignment` - type: STRING - Controls alignment on the X axis. - Valid values are `left`, `center` or `right` @@ -1277,7 +1279,9 @@ Properties: * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value can get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. + - Default is `0.045` * `horizontalAlignment` - type: STRING - Controls alignment on the X axis. - Valid values are `left`, `center` or `right` @@ -1348,7 +1352,9 @@ Properties: * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value can get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. + - Default is `0.045` * `color` - type: COLOR * `backgroundColor` - type: COLOR * `horizontalAlignment` - type: STRING @@ -1562,7 +1568,7 @@ Properties: * `fontPath` - type: PATH - Path to a TrueType font (.ttf) used as fallback if there is no `staticItem` / `itemType` image defined or found, and if `defaultItem` has not been defined. * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. - Default is `0.085` * `letterCase` - type: STRING - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` @@ -1625,6 +1631,8 @@ Properties: - Secondary color; what this means depends on the text list. For example, for game lists, it is the color of a folder. * `fontPath` - type: PATH * `fontSize` - type: FLOAT + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. + - Default is `0.045` * `horizontalAlignment` - type: STRING - Controls alignment on the X axis. - Valid values are `left`, `center` or `right` @@ -1706,7 +1714,8 @@ Properties: - Default is the same value as iconColor. * `fontPath` - type: PATH * `fontSize` - type: FLOAT - - This property also implicitly sets the icon size and is therefore the means to change the overall size of the helpsystem element. + - This property implicitly sets the icon size and is therefore the means to change the overall size of the helpsystem element. This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value can get clamped to a larger relative size. - Default is `0.035` * `entrySpacing` - type: FLOAT - Spacing between the help element pairs. From d74966a3c43d4a359e378a30583918655eae4fce Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 11 Oct 2022 18:07:56 +0200 Subject: [PATCH 20/56] Fixed a heisenbug where letters would sometimes get rendered with ugly edge artifacts. --- es-core/src/resources/Font.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 28cfb1fef..49bebae62 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -519,7 +519,8 @@ bool Font::FontTexture::findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out if (writePos.x + size.x >= textureSize.x && writePos.y + rowHeight + size.y + 1 < textureSize.y) { // Row is full, but the glyph should fit on the next row so move the cursor there. - // Leave 1px of space between glyphs. + // Leave 1 pixel of space between glyphs so that pixels from adjacent glyphs will + // not get sampled during scaling which would lead to edge artifacts. writePos = glm::ivec2 {0, writePos.y + rowHeight + 1}; rowHeight = 0; } @@ -528,7 +529,7 @@ bool Font::FontTexture::findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out return false; // No it still won't fit. cursor_out = writePos; - // Leave 1px of space between glyphs. + // Leave 1 pixel of space between glyphs. writePos.x += size.x + 1; if (size.y > rowHeight) @@ -540,9 +541,13 @@ bool Font::FontTexture::findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out void Font::FontTexture::initTexture() { assert(textureId == 0); + // Create a black texture with zero alpha value so that the single-pixel spaces between the + // glyphs will not be visible. That would otherwise lead to edge artifacts as these pixels + // would get sampled during scaling. + std::vector texture(textureSize.x * textureSize.y * 4, 0); textureId = Renderer::getInstance()->createTexture(Renderer::TextureType::RED, true, false, false, - false, textureSize.x, textureSize.y, nullptr); + false, textureSize.x, textureSize.y, &texture[0]); } void Font::FontTexture::deinitTexture() From 11b035affada5bf9ad70f99499b7cff12691ecdf Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 11 Oct 2022 18:08:57 +0200 Subject: [PATCH 21/56] Fixed a rounding issue in TextComponent. --- es-core/src/components/TextComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index f0449230d..565d33dbd 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -194,7 +194,7 @@ void TextComponent::render(const glm::mat4& parentTrans) } else { // If height is smaller than the font height, then always center vertically. - yOff = std::round((getSize().y - textSize.y) / 2.0f); + yOff = (getSize().y - textSize.y) / 2.0f; } // Draw the overall textbox area. If we're inside a scrollable container then this From 7e923cb9b60a58b60bc600e4199d0d407d99794f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 11 Oct 2022 18:11:36 +0200 Subject: [PATCH 22/56] Some refactoring of the OpenGL renderer and TextureDataManager. --- es-core/src/renderers/Renderer.cpp | 16 +++++++------- es-core/src/renderers/Renderer.h | 2 +- es-core/src/renderers/RendererOpenGL.cpp | 22 +++++++------------ es-core/src/renderers/RendererOpenGL.h | 23 +++++++++++--------- es-core/src/resources/TextureDataManager.cpp | 8 +------ es-core/src/resources/TextureDataManager.h | 3 +-- 6 files changed, 32 insertions(+), 42 deletions(-) diff --git a/es-core/src/renderers/Renderer.cpp b/es-core/src/renderers/Renderer.cpp index d02c07f95..ef2da3ef7 100644 --- a/es-core/src/renderers/Renderer.cpp +++ b/es-core/src/renderers/Renderer.cpp @@ -52,7 +52,7 @@ void Renderer::setIcon() // Try creating SDL surface from logo data. SDL_Surface* logoSurface {SDL_CreateRGBSurfaceFrom( static_cast(rawData.data()), static_cast(width), static_cast(height), - 32, static_cast((width * 4)), rmask, gmask, bmask, amask)}; + 32, static_cast(width * 4), rmask, gmask, bmask, amask)}; if (logoSurface != nullptr) { SDL_SetWindowIcon(mSDLWindow, logoSurface); @@ -147,7 +147,7 @@ bool Renderer::createWindow() if (mWindowWidth != displayMode.w || mWindowHeight != displayMode.h) userResolution = true; - unsigned int windowFlags; + unsigned int windowFlags {0}; setup(); #if defined(_WIN64) @@ -201,7 +201,7 @@ bool Renderer::createWindow() // instead we simply indicate the physical pixel dimensions in parenthesis in the log // file and make sure to double the window and screen sizes in case of a high DPI // display so that the full application window is used for rendering. - int width = 0; + int width {0}; SDL_GL_GetDrawableSize(mSDLWindow, &width, nullptr); int scaleFactor = static_cast(width / mWindowWidth); @@ -309,11 +309,11 @@ void Renderer::pushClipRect(const glm::ivec2& pos, const glm::ivec2& size) box.h = sScreenHeight - box.y; if (mScreenRotated) { - box = Rect(mWindowWidth - mScreenOffsetX - box.x - box.w, - mWindowHeight - mScreenOffsetY - box.y - box.h, box.w, box.h); + box = Rect {mWindowWidth - mScreenOffsetX - box.x - box.w, + mWindowHeight - mScreenOffsetY - box.y - box.h, box.w, box.h}; } else { - box = Rect(mScreenOffsetX + box.x, mScreenOffsetY + box.y, box.w, box.h); + box = Rect {mScreenOffsetX + box.x, mScreenOffsetY + box.y, box.w, box.h}; } // Make sure the box fits within mClipStack.top(), and clip further accordingly. @@ -349,7 +349,7 @@ void Renderer::popClipRect() mClipStack.pop(); if (mClipStack.empty()) - setScissor(Rect(0, 0, 0, 0)); + setScissor(Rect {0, 0, 0, 0}); else setScissor(mClipStack.top()); } @@ -360,7 +360,7 @@ void Renderer::drawRect(const float x, const float h, const unsigned int color, const unsigned int colorEnd, - bool horizontalGradient, + const bool horizontalGradient, const float opacity, const float dimming, const BlendFactor srcBlendFactor, diff --git a/es-core/src/renderers/Renderer.h b/es-core/src/renderers/Renderer.h index cc763a43c..c07962ef9 100644 --- a/es-core/src/renderers/Renderer.h +++ b/es-core/src/renderers/Renderer.h @@ -146,7 +146,7 @@ public: const float h, const unsigned int color, const unsigned int colorEnd, - bool horizontalGradient = false, + const bool horizontalGradient = false, const float opacity = 1.0, const float dimming = 1.0, const BlendFactor srcBlendFactor = BlendFactor::SRC_ALPHA, diff --git a/es-core/src/renderers/RendererOpenGL.cpp b/es-core/src/renderers/RendererOpenGL.cpp index b26bc6935..2dcce598a 100644 --- a/es-core/src/renderers/RendererOpenGL.cpp +++ b/es-core/src/renderers/RendererOpenGL.cpp @@ -33,19 +33,13 @@ RendererOpenGL::RendererOpenGL() noexcept { } -RendererOpenGL::~RendererOpenGL() -{ - for (auto it = mShaderProgramVector.cbegin(); it != mShaderProgramVector.cend(); ++it) - delete *it; -} - RendererOpenGL* RendererOpenGL::getInstance() { static RendererOpenGL instance; return &instance; } -ShaderOpenGL* RendererOpenGL::getShaderProgram(unsigned int shaderID) +std::shared_ptr RendererOpenGL::getShaderProgram(unsigned int shaderID) { unsigned int index {0}; @@ -67,13 +61,13 @@ bool RendererOpenGL::loadShaders() LOG(LogInfo) << "Loading shaders..."; std::vector shaderFiles; - shaderFiles.push_back(":/shaders/glsl/core.glsl"); - shaderFiles.push_back(":/shaders/glsl/blur_horizontal.glsl"); - shaderFiles.push_back(":/shaders/glsl/blur_vertical.glsl"); - shaderFiles.push_back(":/shaders/glsl/scanlines.glsl"); + shaderFiles.emplace_back(":/shaders/glsl/core.glsl"); + shaderFiles.emplace_back(":/shaders/glsl/blur_horizontal.glsl"); + shaderFiles.emplace_back(":/shaders/glsl/blur_vertical.glsl"); + shaderFiles.emplace_back(":/shaders/glsl/scanlines.glsl"); for (auto it = shaderFiles.cbegin(); it != shaderFiles.cend(); ++it) { - ShaderOpenGL* loadShader = new ShaderOpenGL(); + auto loadShader = std::make_shared(); loadShader->loadShaderFile(*it, GL_VERTEX_SHADER); loadShader->loadShaderFile(*it, GL_FRAGMENT_SHADER); @@ -83,7 +77,7 @@ bool RendererOpenGL::loadShaders() return false; } - mShaderProgramVector.push_back(loadShader); + mShaderProgramVector.emplace_back(std::move(loadShader)); } return true; @@ -576,7 +570,7 @@ void RendererOpenGL::shaderPostprocessing(unsigned int shaders, for (size_t i = 0; i < shaderList.size(); ++i) { vertices->shaders = shaderList[i]; - int shaderPasses = 1; + int shaderPasses {1}; // For the blur shaders there is an optional variable to set the number of passes // to execute, which proportionally affects the blur amount. if (shaderList[i] == Renderer::Shader::BLUR_HORIZONTAL || diff --git a/es-core/src/renderers/RendererOpenGL.h b/es-core/src/renderers/RendererOpenGL.h index 5cfaaa06a..d6574e0dc 100644 --- a/es-core/src/renderers/RendererOpenGL.h +++ b/es-core/src/renderers/RendererOpenGL.h @@ -20,15 +20,14 @@ #include #endif +#include + class RendererOpenGL : public Renderer { public: - RendererOpenGL() noexcept; - ~RendererOpenGL(); - static RendererOpenGL* getInstance(); - ShaderOpenGL* getShaderProgram(unsigned int shaderID); + std::shared_ptr getShaderProgram(unsigned int shaderID); bool loadShaders() override; GLenum convertBlendFactor(const BlendFactor BlendFactor); @@ -73,7 +72,9 @@ public: unsigned char* textureRGBA = nullptr) override; private: - std::vector mShaderProgramVector; + RendererOpenGL() noexcept; + + std::vector> mShaderProgramVector; GLuint mShaderFBO1; GLuint mShaderFBO2; GLuint mVertexBuffer1; @@ -83,14 +84,16 @@ private: GLuint mWhiteTexture; GLuint mPostProcTexture1; GLuint mPostProcTexture2; - ShaderOpenGL* mCoreShader; - ShaderOpenGL* mBlurHorizontalShader; - ShaderOpenGL* mBlurVerticalShader; - ShaderOpenGL* mScanlinelShader; - ShaderOpenGL* mLastShader; + std::shared_ptr mCoreShader; + std::shared_ptr mBlurHorizontalShader; + std::shared_ptr mBlurVerticalShader; + std::shared_ptr mScanlinelShader; + std::shared_ptr mLastShader; int mMajorGLVersion; int mMinorGLVersion; + + friend Renderer; }; #endif // ES_CORE_RENDERER_RENDERER_OPENGL_H diff --git a/es-core/src/resources/TextureDataManager.cpp b/es-core/src/resources/TextureDataManager.cpp index f91d11319..eeb1f5533 100644 --- a/es-core/src/resources/TextureDataManager.cpp +++ b/es-core/src/resources/TextureDataManager.cpp @@ -24,13 +24,7 @@ TextureDataManager::TextureDataManager() data[i * 4 + 3] = 0; } mBlank->initFromRGBA(data, 5, 5); - mLoader = new TextureLoader; -} - -TextureDataManager::~TextureDataManager() -{ - // Delete TextureLoader object when destroyed. - delete mLoader; + mLoader = std::make_unique(); } std::shared_ptr TextureDataManager::add(const TextureResource* key, bool tiled) diff --git a/es-core/src/resources/TextureDataManager.h b/es-core/src/resources/TextureDataManager.h index d1a1b03ef..a235314c9 100644 --- a/es-core/src/resources/TextureDataManager.h +++ b/es-core/src/resources/TextureDataManager.h @@ -63,7 +63,6 @@ class TextureDataManager { public: TextureDataManager(); - ~TextureDataManager(); std::shared_ptr add(const TextureResource* key, bool tiled); @@ -90,7 +89,7 @@ private: std::map>::const_iterator> mTextureLookup; std::shared_ptr mBlank; - TextureLoader* mLoader; + std::unique_ptr mLoader; }; #endif // ES_CORE_RESOURCES_TEXTURE_DATA_MANAGER_H From 8bbaec229f9227d5a983ee41798043a72907c154 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 11 Oct 2022 19:37:35 +0200 Subject: [PATCH 23/56] Fixed a rounding issue in TextComponent. --- es-core/src/components/TextComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index 565d33dbd..56961e4b9 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -184,7 +184,7 @@ void TextComponent::render(const glm::mat4& parentTrans) break; } case ALIGN_CENTER: { - yOff = (getSize().y - textSize.y) / 2.0f; + yOff = std::round(getSize().y - textSize.y) / 2.0f; break; } default: { @@ -194,7 +194,7 @@ void TextComponent::render(const glm::mat4& parentTrans) } else { // If height is smaller than the font height, then always center vertically. - yOff = (getSize().y - textSize.y) / 2.0f; + yOff = std::round(getSize().y - textSize.y) / 2.0f; } // Draw the overall textbox area. If we're inside a scrollable container then this From 7f5ed1c41db1499b3910c9657aa1a4cc0d86fc9d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 11 Oct 2022 21:31:05 +0200 Subject: [PATCH 24/56] Fixed a minor graphical glitch in CarouselComponent. --- es-core/src/components/primary/CarouselComponent.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/es-core/src/components/primary/CarouselComponent.h b/es-core/src/components/primary/CarouselComponent.h index 14f372a7d..359da658e 100644 --- a/es-core/src/components/primary/CarouselComponent.h +++ b/es-core/src/components/primary/CarouselComponent.h @@ -695,6 +695,10 @@ template void CarouselComponent::render(const glm::mat4& parentT yOff += mSize.y * mVerticalOffset; } + // This is necessary to avoid single-pixel horizontal jumps at the end positions. + if (mType != CarouselType::HORIZONTAL) + xOff = std::round(xOff); + int center {0}; int centerOffset {0}; // Needed to make sure that overlapping items are renderered correctly. From 3351b8c41aa5ab4404dbf606a412fe51517d7d60 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 12 Oct 2022 22:15:14 +0200 Subject: [PATCH 25/56] Added a %GAMEDIR% variable to the -rompath option for all MAME standalone entries. Also fixed an issue where the cps system -rompath option for MAME standalone pointed to the wrong system directory. --- resources/systems/macos/es_systems.xml | 26 ++++++++++++------------ resources/systems/unix/es_systems.xml | 26 ++++++++++++------------ resources/systems/windows/es_systems.xml | 26 ++++++++++++------------ 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/resources/systems/macos/es_systems.xml b/resources/systems/macos/es_systems.xml index da626c205..760359ce3 100644 --- a/resources/systems/macos/es_systems.xml +++ b/resources/systems/macos/es_systems.xml @@ -85,7 +85,7 @@ %ROMPATH%/apple2 .do .DO .dsk .DSK .nib .NIB .po .PO %EMULATOR_MEDNAFEN% -force_module apple2 %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/apple2 apple2e -flop1 %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/apple2 apple2e -flop1 %ROM% apple2 apple2 @@ -94,7 +94,7 @@ Apple IIGS %ROMPATH%/apple2gs .2mg .2MG - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/apple2gs apple2gs -flop3 %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/apple2gs apple2gs -flop3 %ROM% apple2gs apple2gs @@ -107,7 +107,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/arcade %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/arcade %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/flycast_libretro.dylib %ROM% @@ -121,7 +121,7 @@ %ROMPATH%/astrocde .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/astrocde astrocde -cart %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/astrocde astrocde -cart %BASENAME% astrocde astrocade @@ -325,7 +325,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/arcade %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/cps %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.dylib %ROM% @@ -491,8 +491,8 @@ %ROMPATH%/gameandwatch .mgw .MGW .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/gw_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/gameandwatch %BASENAME% - %STARTDIR%=~/.mame %EMULATOR_MAME% -artpath %ROMPATH%/gameandwatch/artwork -rompath %ROMPATH%/gameandwatch %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/gameandwatch %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -artpath %ROMPATH%/gameandwatch/artwork -rompath %GAMEDIR%\;%ROMPATH%/gameandwatch %BASENAME% gameandwatch gameandwatch @@ -590,7 +590,7 @@ .bin .BIN .cdt .CDT .cpr .CPR .dsk .DSK .kcr .KCR .m3u .M3U .sna .SNA .tap .TAR .voc .VOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/cap32_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/crocods_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/gx4000 gx4000 -cart %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/gx4000 gx4000 -cart %ROM% gx4000 gx4000 @@ -658,7 +658,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/mame %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/mame %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/flycast_libretro.dylib %ROM% @@ -927,7 +927,7 @@ %ROMPATH%/neogeo .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/neogeo %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/neogeo %BASENAME% neogeo neogeo @@ -938,7 +938,7 @@ .chd .CHD .cue .CUE %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/neocd_libretro.dylib %ROM% %EMULATOR_RETROARCH% --subsystem neocd -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/neogeocd neocdz -cdrm %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/neogeocd neocdz -cdrm %ROM% neogeocd neogeocd @@ -949,7 +949,7 @@ .chd .CHD .cue .CUE %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/neocd_libretro.dylib %ROM% %EMULATOR_RETROARCH% --subsystem neocd -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/neogeocdjp neocdz -cdrm %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/neogeocdjp neocdz -cdrm %ROM% neogeocd neogeocdjp @@ -1486,7 +1486,7 @@ Texas Instruments TI-99 %ROMPATH%/ti99 .rpk .RPK .7z .7Z .zip .ZIP - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/ti99 ti99_4a -ioport peb -ioport:peb:slot3 speech -cart %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/ti99 ti99_4a -ioport peb -ioport:peb:slot3 speech -cart %BASENAME% ti99 ti99 diff --git a/resources/systems/unix/es_systems.xml b/resources/systems/unix/es_systems.xml index 823e2a06c..7e87ad419 100644 --- a/resources/systems/unix/es_systems.xml +++ b/resources/systems/unix/es_systems.xml @@ -86,7 +86,7 @@ .do .DO .dsk .DSK .nib .NIB .po .PO %EMULATOR_LINAPPLE% -f -b --d1 %ROM% %EMULATOR_MEDNAFEN% -force_module apple2 %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/apple2 apple2e -flop1 %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/apple2 apple2e -flop1 %ROM% apple2 apple2 @@ -95,7 +95,7 @@ Apple IIGS %ROMPATH%/apple2gs .2mg .2MG - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/apple2gs apple2gs -flop3 %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/apple2gs apple2gs -flop3 %ROM% apple2gs apple2gs @@ -108,7 +108,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.so %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/arcade %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/arcade %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/flycast_libretro.so %ROM% @@ -123,7 +123,7 @@ %ROMPATH%/astrocde .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.so %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/astrocde astrocde -cart %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/astrocde astrocde -cart %BASENAME% astrocde astrocade @@ -327,7 +327,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.so %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/arcade %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/cps %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.so %ROM% @@ -499,8 +499,8 @@ %ROMPATH%/gameandwatch .mgw .MGW .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/gw_libretro.so %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/gameandwatch %BASENAME% - %STARTDIR%=~/.mame %EMULATOR_MAME% -artpath %ROMPATH%/gameandwatch/artwork -rompath %ROMPATH%/gameandwatch %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/gameandwatch %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -artpath %ROMPATH%/gameandwatch/artwork -rompath %GAMEDIR%\;%ROMPATH%/gameandwatch %BASENAME% gameandwatch gameandwatch @@ -601,7 +601,7 @@ .bin .BIN .cdt .CDT .cpr .CPR .dsk .DSK .kcr .KCR .m3u .M3U .sna .SNA .tap .TAR .voc .VOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/cap32_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/crocods_libretro.so %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/gx4000 gx4000 -cart %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/gx4000 gx4000 -cart %ROM% gx4000 gx4000 @@ -669,7 +669,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.so %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/mame %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/mame %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/flycast_libretro.so %ROM% @@ -947,7 +947,7 @@ .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.so %ROM% %EMULATOR_FINALBURN-NEO% -fullscreen %BASENAME% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/neogeo %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/neogeo %BASENAME% neogeo neogeo @@ -959,7 +959,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/neocd_libretro.so %ROM% %EMULATOR_RETROARCH% --subsystem neocd -L %CORE_RETROARCH%/fbneo_libretro.so %ROM% %EMULATOR_FINALBURN-NEO% neocdz -fullscreen -cd %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/neogeocd neocdz -cdrm %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/neogeocd neocdz -cdrm %ROM% neogeocd neogeocd @@ -971,7 +971,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/neocd_libretro.so %ROM% %EMULATOR_RETROARCH% --subsystem neocd -L %CORE_RETROARCH%/fbneo_libretro.so %ROM% %EMULATOR_FINALBURN-NEO% neocdz -fullscreen -cd %ROM% - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/neogeocdjp neocdz -cdrm %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/neogeocdjp neocdz -cdrm %ROM% neogeocd neogeocdjp @@ -1522,7 +1522,7 @@ Texas Instruments TI-99 %ROMPATH%/ti99 .rpk .RPK .7z .7Z .zip .ZIP - %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %ROMPATH%/ti99 ti99_4a -ioport peb -ioport:peb:slot3 speech -cart %BASENAME% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/ti99 ti99_4a -ioport peb -ioport:peb:slot3 speech -cart %BASENAME% ti99 ti99 diff --git a/resources/systems/windows/es_systems.xml b/resources/systems/windows/es_systems.xml index 7ca6d25dd..828940d95 100644 --- a/resources/systems/windows/es_systems.xml +++ b/resources/systems/windows/es_systems.xml @@ -86,7 +86,7 @@ .do .DO .dsk .DSK .nib .NIB .po .PO %EMULATOR_APPLEWIN% -f -d1 %ROM% %EMULATOR_MEDNAFEN% -force_module apple2 %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\apple2 apple2e -flop1 %ROM% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\apple2 apple2e -flop1 %ROM% apple2 apple2 @@ -95,7 +95,7 @@ Apple IIGS %ROMPATH%\apple2gs .2mg .2MG - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\apple2gs apple2gs -flop3 %ROM% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\apple2gs apple2gs -flop3 %ROM% apple2gs apple2gs @@ -108,7 +108,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2000_libretro.dll %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\arcade %BASENAME% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\arcade %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbneo_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\flycast_libretro.dll %ROM% @@ -126,7 +126,7 @@ %ROMPATH%\astrocde .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame_libretro.dll %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\astrocde astrocde -cart %BASENAME% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\astrocde astrocde -cart %BASENAME% astrocde astrocade @@ -330,7 +330,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2000_libretro.dll %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\arcade %BASENAME% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\cps %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbneo_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps1_libretro.dll %ROM% @@ -500,8 +500,8 @@ %ROMPATH%\gameandwatch .mgw .MGW .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\gw_libretro.dll %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\gameandwatch %BASENAME% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -artpath %ROMPATH%\gameandwatch\artwork -rompath %ROMPATH%\gameandwatch %BASENAME% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\gameandwatch %BASENAME% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -artpath %ROMPATH%\gameandwatch\artwork -rompath %GAMEDIR%\;%ROMPATH%\gameandwatch %BASENAME% gameandwatch gameandwatch @@ -601,7 +601,7 @@ .bin .BIN .cdt .CDT .cpr .CPR .dsk .DSK .kcr .KCR .m3u .M3U .sna .SNA .tap .TAR .voc .VOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\cap32_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\crocods_libretro.dll %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\gx4000 gx4000 -cart %ROM% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\gx4000 gx4000 -cart %ROM% gx4000 gx4000 @@ -670,7 +670,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2000_libretro.dll %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\mame %BASENAME% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\mame %BASENAME% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbneo_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\flycast_libretro.dll %ROM% @@ -950,7 +950,7 @@ .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbneo_libretro.dll %ROM% %STARTDIR%=%EMUDIR% %EMULATOR_FINALBURN-NEO% %BASENAME% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\neogeo %BASENAME% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\neogeo %BASENAME% neogeo neogeo @@ -961,7 +961,7 @@ .chd .CHD .cue .CUE %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\neocd_libretro.dll %ROM% %EMULATOR_RETROARCH% --subsystem neocd -L %CORE_RETROARCH%\fbneo_libretro.dll %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\neogeocd neocdz -cdrm %ROM% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\neogeocd neocdz -cdrm %ROM% neogeocd neogeocd @@ -972,7 +972,7 @@ .chd .CHD .cue .CUE %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\neocd_libretro.dll %ROM% %EMULATOR_RETROARCH% --subsystem neocd -L %CORE_RETROARCH%\fbneo_libretro.dll %ROM% - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\neogeocdjp neocdz -cdrm %ROM% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\neogeocdjp neocdz -cdrm %ROM% neogeocd neogeocdjp @@ -1524,7 +1524,7 @@ Texas Instruments TI-99 %ROMPATH%\ti99 .rpk .RPK .7z .7Z .zip .ZIP - %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %ROMPATH%\ti99 ti99_4a -ioport peb -ioport:peb:slot3 speech -cart %BASENAME% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\ti99 ti99_4a -ioport peb -ioport:peb:slot3 speech -cart %BASENAME% ti99 ti99 From a6546a43abe6f475f55f3e32e38b7d05c33a525d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 12 Oct 2022 22:19:32 +0200 Subject: [PATCH 26/56] (Windows) Added a -force-feedback option and an %INJECT% variable to the Supermodel emulator for the arcade, mame and model3 systems. --- resources/systems/windows/es_systems.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/systems/windows/es_systems.xml b/resources/systems/windows/es_systems.xml index 828940d95..0126a46c7 100644 --- a/resources/systems/windows/es_systems.xml +++ b/resources/systems/windows/es_systems.xml @@ -116,7 +116,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\kronos_libretro.dll %ROM% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME% %STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME% - %STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen %ROM% + %STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM% arcade arcade @@ -678,7 +678,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\kronos_libretro.dll %ROM% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME% %STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME% - %STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen %ROM% + %STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM% arcade mame @@ -786,7 +786,7 @@ Sega Model 3 %ROMPATH%\model3 .7z .7Z .zip .ZIP - %STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen %ROM% + %STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM% arcade model3 From 3759f6873a00dc2540589e5ed07fe15a4f7959b3 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 12 Oct 2022 22:30:23 +0200 Subject: [PATCH 27/56] (Linux) Added the Supermodel standalone emulator for the arcade, mame and model3 systems. --- resources/systems/unix/es_find_rules.xml | 11 +++++++++++ resources/systems/unix/es_systems.xml | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/resources/systems/unix/es_find_rules.xml b/resources/systems/unix/es_find_rules.xml index b6e03dfce..8d6c00a0e 100644 --- a/resources/systems/unix/es_find_rules.xml +++ b/resources/systems/unix/es_find_rules.xml @@ -576,6 +576,17 @@ steam + + + + supermodel + + + ~/Applications/Supermodel/supermodel + ~/.local/bin/Supermodel/supermodel + ~/bin/Supermodel/supermodel + + diff --git a/resources/systems/unix/es_systems.xml b/resources/systems/unix/es_systems.xml index 7e87ad419..5a6d13e2a 100644 --- a/resources/systems/unix/es_systems.xml +++ b/resources/systems/unix/es_systems.xml @@ -114,6 +114,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/flycast_libretro.so %ROM% %EMULATOR_FLYCAST% %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/kronos_libretro.so %ROM% + %STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -force-feedback %INJECT%=%BASENAME%.commands %ROM% arcade arcade @@ -675,6 +676,7 @@ %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/flycast_libretro.so %ROM% %EMULATOR_FLYCAST% %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/kronos_libretro.so %ROM% + %STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -force-feedback %INJECT%=%BASENAME%.commands %ROM% arcade mame @@ -782,7 +784,7 @@ Sega Model 3 %ROMPATH%/model3 .7z .7Z .zip .ZIP - PLACEHOLDER %ROM% + %STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -force-feedback %INJECT%=%BASENAME%.commands %ROM% arcade model3 From 681218f55ad8cc5041ff3e3c664bd631c7379554 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 13 Oct 2022 22:08:36 +0200 Subject: [PATCH 28/56] Added support for the Sega Model 2 (model2) game system on Linux on macOS. --- resources/systems/macos/es_systems.xml | 3 ++- resources/systems/unix/es_systems.xml | 3 ++- resources/systems/windows/es_systems.xml | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/systems/macos/es_systems.xml b/resources/systems/macos/es_systems.xml index 760359ce3..81a371f23 100644 --- a/resources/systems/macos/es_systems.xml +++ b/resources/systems/macos/es_systems.xml @@ -760,7 +760,8 @@ Sega Model 2 %ROMPATH%/model2 .7z .7Z .zip .ZIP - PLACEHOLDER %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.dylib %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/model2 %BASENAME% arcade model2 diff --git a/resources/systems/unix/es_systems.xml b/resources/systems/unix/es_systems.xml index 5a6d13e2a..3ea95dbfe 100644 --- a/resources/systems/unix/es_systems.xml +++ b/resources/systems/unix/es_systems.xml @@ -775,7 +775,8 @@ Sega Model 2 %ROMPATH%/model2 .7z .7Z .zip .ZIP - PLACEHOLDER %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.so %ROM% + %STARTDIR%=~/.mame %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%/model2 %BASENAME% arcade model2 diff --git a/resources/systems/windows/es_systems.xml b/resources/systems/windows/es_systems.xml index 0126a46c7..c5799f763 100644 --- a/resources/systems/windows/es_systems.xml +++ b/resources/systems/windows/es_systems.xml @@ -778,6 +778,8 @@ .7z .7Z .zip .ZIP %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME% %STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame_libretro.dll %ROM% + %HIDEWINDOW% %RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_MAME% -rompath %GAMEDIR%\;%ROMPATH%\model2 %BASENAME% arcade model2 From 5f6bd48022e9d129afc48de1f21385d4bb8b405b Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 14 Oct 2022 18:12:23 +0200 Subject: [PATCH 29/56] One more try to fix a rounding issue in TextComponent. --- es-core/src/components/TextComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index 56961e4b9..ea32207ff 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -184,7 +184,7 @@ void TextComponent::render(const glm::mat4& parentTrans) break; } case ALIGN_CENTER: { - yOff = std::round(getSize().y - textSize.y) / 2.0f; + yOff = std::round((getSize().y - textSize.y) / 2.0f); break; } default: { @@ -194,7 +194,7 @@ void TextComponent::render(const glm::mat4& parentTrans) } else { // If height is smaller than the font height, then always center vertically. - yOff = std::round(getSize().y - textSize.y) / 2.0f; + yOff = std::round((getSize().y - textSize.y) / 2.0f); } // Draw the overall textbox area. If we're inside a scrollable container then this From 16c4fb6d176ca71d0e412b2ba70e6a3bcb923ec4 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 14 Oct 2022 18:14:15 +0200 Subject: [PATCH 30/56] Fixed some alignment issues in CarouselComponent. --- .../components/primary/CarouselComponent.h | 42 +++++++++---------- es-core/src/resources/Font.cpp | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/es-core/src/components/primary/CarouselComponent.h b/es-core/src/components/primary/CarouselComponent.h index 359da658e..505c0e18d 100644 --- a/es-core/src/components/primary/CarouselComponent.h +++ b/es-core/src/components/primary/CarouselComponent.h @@ -183,7 +183,8 @@ CarouselComponent::CarouselComponent() , mMaxItemCount {3.0f} , mItemsBeforeCenter {8} , mItemsAfterCenter {8} - , mItemSize {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f} + , mItemSize {glm::round( + glm::vec2 {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f})} , mLinearInterpolation {false} , mInstantItemTransitions {false} , mItemAxisHorizontal {false} @@ -226,7 +227,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr(false, dynamic); item->setLinearInterpolation(mLinearInterpolation); item->setMipmapping(true); - item->setMaxSize(mItemSize * mItemScale); + item->setMaxSize(glm::round(mItemSize * mItemScale)); item->applyTheme(theme, "system", "image_logo", ThemeFlags::PATH | ThemeFlags::COLOR); item->setRotateByTargetSize(true); @@ -240,7 +241,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr(false, dynamic); item->setLinearInterpolation(mLinearInterpolation); item->setMipmapping(true); - item->setMaxSize(mItemSize * mItemScale); + item->setMaxSize(glm::round(mItemSize * mItemScale)); item->setImage(entry.data.itemPath); item->applyTheme(theme, "system", "", ThemeFlags::ALL); item->setRotateByTargetSize(true); @@ -251,7 +252,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr(false, dynamic); defaultItem->setLinearInterpolation(mLinearInterpolation); defaultItem->setMipmapping(true); - defaultItem->setMaxSize(mItemSize * mItemScale); + defaultItem->setMaxSize(glm::round(mItemSize * mItemScale)); defaultItem->setImage(entry.data.defaultItemPath); defaultItem->applyTheme(theme, "system", "", ThemeFlags::ALL); defaultItem->setRotateByTargetSize(true); @@ -275,7 +276,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr( nameEntry, mFont, 0x000000FF, mItemHorizontalAlignment, mItemVerticalAlignment, - glm::vec3 {0.0f, 0.0f, 0.0f}, mItemSize * mItemScale, 0x00000000); + glm::vec3 {0.0f, 0.0f, 0.0f}, glm::round(mItemSize * mItemScale), 0x00000000); if (legacyMode) { text->applyTheme(theme, "system", "text_logoText", ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | @@ -310,7 +311,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptrsetOrigin(entry.data.item->getOrigin().x, 0.5f); - glm::vec2 denormalized {mItemSize * entry.data.item->getOrigin()}; + glm::vec2 denormalized {glm::round(mItemSize * entry.data.item->getOrigin())}; entry.data.item->setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f}); List::add(entry); @@ -323,7 +324,7 @@ void CarouselComponent::updateEntry(Entry& entry, const std::shared_ptr(false, true); item->setLinearInterpolation(mLinearInterpolation); item->setMipmapping(true); - item->setMaxSize(mItemSize * mItemScale); + item->setMaxSize(glm::round(mItemSize * mItemScale)); item->setImage(entry.data.itemPath); item->applyTheme(theme, "system", "", ThemeFlags::ALL); item->setRotateByTargetSize(true); @@ -348,7 +349,7 @@ void CarouselComponent::updateEntry(Entry& entry, const std::shared_ptrsetOrigin(entry.data.item->getOrigin().x, 0.5f); - glm::vec2 denormalized {mItemSize * entry.data.item->getOrigin()}; + glm::vec2 denormalized {glm::round(mItemSize * entry.data.item->getOrigin())}; entry.data.item->setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f}); } @@ -609,7 +610,7 @@ template void CarouselComponent::render(const glm::mat4& parentT float scaleSize {mItemSize.x * mItemScale - mItemSize.x}; if (isWheel) { - xOff = (mSize.x - mItemSize.x) / 2.0f - (camOffset * itemSpacing.y); + xOff = (mSize.x - mItemSize.x) / 2.0f; yOff = (mSize.y - mItemSize.y) / 2.0f; // Alignment of the actual carousel inside to the overall component area. if (mLegacyMode) { @@ -649,7 +650,8 @@ template void CarouselComponent::render(const glm::mat4& parentT } } else if (mType == CarouselType::VERTICAL) { - itemSpacing.y = ((mSize.y - (mItemSize.y * mMaxItemCount)) / mMaxItemCount) + mItemSize.y; + itemSpacing.y = + std::round(((mSize.y - (mItemSize.y * mMaxItemCount)) / mMaxItemCount) + mItemSize.y); yOff = (mSize.y - mItemSize.y) / 2.0f - (camOffset * itemSpacing.y); if (mItemHorizontalAlignment == ALIGN_LEFT) { if (mLegacyMode) @@ -668,7 +670,8 @@ template void CarouselComponent::render(const glm::mat4& parentT } } else { // HORIZONTAL. - itemSpacing.x = ((mSize.x - (mItemSize.x * mMaxItemCount)) / mMaxItemCount) + mItemSize.x; + itemSpacing.x = + std::round(((mSize.x - (mItemSize.x * mMaxItemCount)) / mMaxItemCount) + mItemSize.x); xOff = (mSize.x - mItemSize.x) / 2.0f - (camOffset * itemSpacing.x); if (mItemVerticalAlignment == ALIGN_TOP) { if (mLegacyMode) @@ -695,10 +698,6 @@ template void CarouselComponent::render(const glm::mat4& parentT yOff += mSize.y * mVerticalOffset; } - // This is necessary to avoid single-pixel horizontal jumps at the end positions. - if (mType != CarouselType::HORIZONTAL) - xOff = std::round(xOff); - int center {0}; int centerOffset {0}; // Needed to make sure that overlapping items are renderered correctly. @@ -781,8 +780,9 @@ template void CarouselComponent::render(const glm::mat4& parentT if (singleEntry) itemTrans = glm::translate(carouselTrans, glm::vec3 {xOff, yOff, 0.0f}); else - itemTrans = glm::translate( - itemTrans, glm::vec3 {i * itemSpacing.x + xOff, i * itemSpacing.y + yOff, 0.0f}); + itemTrans = + glm::translate(itemTrans, glm::vec3 {std::round(i * itemSpacing.x + xOff), + std::round(i * itemSpacing.y + yOff), 0.0f}); float opacity {0.0f}; @@ -1011,8 +1011,8 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("itemSize")) { const glm::vec2 itemSize {glm::clamp(elem->get("itemSize"), 0.05f, 1.0f)}; - mItemSize = - itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight()); + mItemSize = glm::round( + itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())); } if (elem->has("itemScale")) @@ -1164,8 +1164,8 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, itemSize.x = glm::clamp(itemSize.x, 0.005f, 1.0f); itemSize.y = glm::clamp(itemSize.y, 0.005f, 1.0f); } - mItemSize = - itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight()); + mItemSize = glm::round( + itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())); } if (elem->has("maxLogoCount")) { diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 49bebae62..8ea08cb42 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -207,7 +207,7 @@ TextCache* Font::buildTextCache(const std::string& text, size_t i {0}; for (auto it = vertMap.cbegin(); it != vertMap.cend(); ++it) { - TextCache::VertexList& vertList = cache->vertexLists.at(i); + TextCache::VertexList& vertList {cache->vertexLists.at(i)}; vertList.textureIdPtr = &it->first->textureId; vertList.verts = it->second; ++i; From 221cd89e4aa7c2bd4eeb55b7ba937be8692f630f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 15 Oct 2022 13:15:55 +0200 Subject: [PATCH 31/56] Documentation update. --- CHANGELOG.md | 13 +++++++++++-- CONTRIBUTING.md | 1 + USERGUIDE-DEV.md | 29 +++++++++++++++++++++-------- USERGUIDE.md | 8 +++++++- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b1493f76..0de945c3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,14 @@ * Added ares standalone as an alternative emulator for many systems * Added MAME standalone as an alternative emulator for the gameandwatch system * Added openMSX standalone as an alternative emulator for the colecovision, msx, msx1, msx2 and msxturbor systems -* (Linux) Added support for the Nintendo Wii U (wiiu) game system +* (Linux) Added support for the Nintendo Wii U (wiiu) game system by adding the Cemu standalone emulator +* (Linux) Added support for the Sega Model 3 (model3) game system by adding the Supermodel standalone emulator +* (Linux) Added Supermodel standalone as an alternative emulator for the arcade and mame systems +* Added support for the Sega Model 2 (model2) game system on Linux on macOS by adding the MAME - Current RetroArch core +* Added MAME standalone as an alternative emulator for the model2 system +* (Windows) Added the MAME - Current RetroArch core as an alternative emulator for the model2 system +* (Windows) Added a -force-feedback option and an %INJECT% variable to the Supermodel emulator for the arcade, mame and model3 systems +* Added a %GAMEDIR% variable to the -rompath option for all MAME standalone entries to allow launching games from subdirectories * Added Triforce (Dolphin fork) standalone as an alternative emulator for the gc system on Linux and Windows * Added simple64 standalone as an alternative emulator for the n64 system on Linux and Windows * (Linux) Added Rosalie's Mupen GUI standalone as an alternative emulator for the n64 system @@ -39,7 +46,7 @@ * Removed the .ccd, .cue and .iso file extensions from the neogeo system * Added the FinalBurn Neo RetroArch core as an alternative emulator for the neogeocd and neogeocdjp systems * Added MAME standalone as an alternative emulator for the neogeo, neogeocd and neogeocdjp systems -* Added FinalBurn Neo standalone as an alternative emulator for the fbneo, neogeo, neogeocd and neogeocdjp systems on Unix +* Added FinalBurn Neo standalone as an alternative emulator for the fbneo, neogeo, neogeocd and neogeocdjp systems on Linux * Added FinalBurn Neo standalone as an alternative emulator for the fbneo and neogeo system on Windows * Set DOSBox-X and DOSBox Staging to start in the game directory so per-game dosbox.conf files can be used * (macOS) Added an additional find rule entry for DOSBox-X as the binary name has been changed @@ -180,6 +187,7 @@ ### Bug fixes * Multiple levels of symlinking in the ROMs directory tree could crash the application on startup +* For the cps system, MAME standalone was configured with the wrong system directory for the -rompath option, pointing to "arcade" instead of "cps" * During some menu operations that reloaded the gamelist view, the cached background could miss some components as they were not rendered in time * Text wrapping did not work correctly for text that typically does not contain spaces, like Japanese * Changing some values using the metadata editor could lead to an incorrect sort order if the changes were done from within a grouped custom collection @@ -202,6 +210,7 @@ * When a legacy theme set had a video view style but did not have a valid md_video entry then the video player would still start (and play the audio) * Clearing a game in the metadata editor would sometimes not remove all media files (if there were both a .jpg and a .png for a certain file type) * The tile property for the image element did not work correctly with SVG images +* Letters would sometimes get rendered with ugly edge artifacts, visible when scaling text on the carousel * Text opacity did not work correctly in some places, such as for the help prompts * ScrollableContainer faded semi-transparent text to fully opaque when resetting * ScrollableContainer faded in the background text color in addition to the text color when resetting diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f529d9583..eec766b98 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,6 +76,7 @@ The roadmap is under constant review so expect it to change from time to time. S #### v2.1 +* Add element transition animations to the theme engine * New texture/cache manager with improved memory management and support for GIF and Lottie animations * Reduced amount of gamelist reloading to retain cached textures and improve overall performance * Add scraping of game manuals and maps and create a viewer for these (with PDF, GIF, JPG and PNG support) diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index ed24d2f7c..30437cbab 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -691,6 +691,7 @@ The following manually downloaded emulators are supported when using the bundled | flash | Lightspark | lightspark/lightspark | | flash | Ruffle | ruffle/ruffle | | fmtowns | Tsugaru | tsugaru/Tsugaru_CUI | +| model3 | Supermodel | Supermodel/supermodel | | oric | Oricutron | oricutron/Oricutron | | pico8 | PICO-8 | pico-8/pico8 | | psvita | Vita3K | Vita3K/Vita3K | @@ -916,7 +917,7 @@ This is required by the TheGamesDB scraper where the expanded filenames are used By default ES-DE will filter out BIOSes and devices that can't be launched directly, meaning these will never show up in the gamelist. But this only applies to files that are listed in the regular MAME driver file and BIOSes and devices for systems like MESS and Model 2 will not be filtered out. You'll instead need to manually hide these files using the _Hidden_ option in the metadata editor. -If emulating Sega Model 2 games using _Model 2 Emulator_, then you need to change the ROM directory path in the EMULATOR.INI file to point to your Model 2 ROMs. If you're using a portable ES-DE installation, then you can set the ROM directory path to be relative, for example: +If emulating Sega Model 2 games using _Model 2 Emulator_ on Windows, then you need to change the ROM directory path in the EMULATOR.INI file to point to your Model 2 ROMs. If you're using a portable ES-DE installation, then you can set the ROM directory path to be relative, for example: ``` [RomDirs] Dir1=..\..\ROMs\arcade\Sega Model 2 @@ -928,6 +929,18 @@ Also note that Model 2 Emulator is a bit broken and on most GPU drivers it will Likewise, if using the standalone release of FinalBurn Neo you also need to define the ROM directory in the fbneo.ini file or via the user interface as this emulator does not support passing the full path to the game ROM on game launch. +On Unix/Linux and macOS, the only available emulator for Sega Model 2 is MAME, either the RetroArch - Current core or MAME standalone. Compatibility is still quite poor with only a handful of games working correctly, but this is likely to improve going forward as almost all games for this platform can already start and run to a certain degree. Some games flagged as not working by MAME are still playable with only minor glitches to audio and graphics, but make sure to use a recent ROM set for maximum compatibility. + +For Sega Model 3 emulation on Linux download the custom [Supermodel_2022-10-07.tar.gz](https://gitlab.com/es-de/emulationstation-de/-/package_files/55835402/download) package for ES-DE and follow the instructions in the Readme.txt file. In summary you need to place the `supermodel` binary into `~/Applications/Supermodel/` and you need to place the `Config` and `NVRAM` directories into `~/ROMs/model3/`. Note that this build does not include network support as that would make it incompatible with SteamOS. Apart from that it runs really well. If you're using a Linux OS with access to the AUR, then you can use that release of Supermodel instead. But if you do, you still need to place your Config and NVRAM directories into ~/ROMs/model3/ so it's a good idea to download the custom build and read the Readme.txt file to fully understand the required setup. + +Although there is a Homebrew release of Supermodel for macOS this seems to be quite old and is apparently not working correctly so for the time being the model3 system is unsupported on this operating system. + +If using the Homebrew release of MAME standalone on macOS and emulating MESS systems like astrocde and ti99, then you need to configure the path to the MAME hash files in the mame.ini file. Alternatively you can symlink the installed hash directory to `~/.mame/` like the following (you will of course need to modify the command depending on which MAME version you have installed): +``` +ln -s /opt/homebrew/Cellar/mame/0.248/share/mame/hash ~/.mame/ # on ARM/Apple +ln -s /usr/local/Cellar/mame/0.248/share/mame/hash ~/.mame/ # on x86/Intel +``` + #### Nintendo Game and Watch There are two ways to play these games, either via simulation or via emulation. @@ -2971,7 +2984,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed | android | Google Android | BlueStacks **(Standalone)** [W] | | No | Shortcut (.lnk) file in root folder | | apple2 | Apple II | LinApple **(Standalone)** [U],
Mednafen **(Standalone)** [M],
AppleWin **(Standalone)** [W*] | Mednafen **(Standalone)** [UW*],
MAME **(Standalone)** [UMW*] | Yes for Mednafen and MAME | See the specific _Apple II_ section elsewhere in this guide | | apple2gs | Apple IIGS | MAME **(Standalone)** [UMW*] | | Yes | See the specific _Apple IIGS_ section elsewhere in this guide | -| arcade | Arcade | MAME - Current | MAME 2010,
MAME 2003-Plus,
MAME 2000,
MAME **(Standalone)** [UMW*],
FinalBurn Neo,
FB Alpha 2012,
Flycast,
Flycast **(Standalone)** [UMW*],
Kronos [UW],
Model 2 Emulator **(Standalone)** [W*],
Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],
Supermodel **(Standalone)** [W*] | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | +| arcade | Arcade | MAME - Current | MAME 2010,
MAME 2003-Plus,
MAME 2000,
MAME **(Standalone)** [UMW*],
FinalBurn Neo,
FB Alpha 2012,
Flycast,
Flycast **(Standalone)** [UMW*],
Kronos [UW],
Model 2 Emulator **(Standalone)** [W*],
Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],
Supermodel **(Standalone)** [UW*] | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | astrocde | Bally Astrocade | MAME - Current | MAME **(Standalone)** [UMW*] | | See the specific _Bally Astrocade_ section elsewhere in this guide | | atari2600 | Atari 2600 | Stella | Stella 2014,
ares **(Standalone)** [UMW*] | No | Single archive or ROM file in root folder | | atari5200 | Atari 5200 | a5200 | Atari800,
Atari800 **(Standalone)** [UMW*] | Yes | | @@ -2993,7 +3006,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed | coco | Tandy Color Computer | XRoar CoCo 2 NTSC **(Standalone)** [UMW*] | XRoar CoCo 2 PAL **(Standalone)** [UMW*] | Yes | See the specific _Tandy Color Computer_ section elsewhere in this guide | | colecovision | ColecoVision | blueMSX | Gearcoleco,
openMSX **(Standalone)** [UMW*],
ares **(Standalone)** [UMW*] | Yes | Single archive or ROM file in root folder | | cps | Capcom Play System | MAME - Current | MAME 2010,
MAME 2003-Plus,
MAME 2000,
MAME **(Standalone)** [UMW*],
FinalBurn Neo,
FB Alpha 2012,
FB Alpha 2012 CPS-1,
FB Alpha 2012 CPS-2,
FB Alpha 2012 CPS-3 | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | -| daphne | Daphne Arcade LaserDisc Emulator | Hypseus [Daphne] **(Standalone)** [UW*] | Hypseus [Singe] **(Standalone)** [UW*] | Yes (Daphne games) | See the specific _Hypseus Singe (Daphne)_ section elsewhere in this guide | +| daphne | Daphne Arcade LaserDisc Emulator | Hypseus [Daphne] **(Standalone)** [UW*] | Hypseus [Singe] **(Standalone)** [UW*] | Yes for Daphne games | See the specific _Hypseus Singe (Daphne)_ section elsewhere in this guide | | desktop | Desktop Applications | _Suspend ES-DE_ | _Keep ES-DE running_ | | See the specific _Ports and desktop applications_ section elsewhere in this guide | | doom | Doom | PrBoom | Boom 3 [UW],
Boom 3 xp [UW],
_Shortcut or script_ | No | | | dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,
DOSBox-SVN,
DOSBox-X **(Standalone)**,
DOSBox Staging **(Standalone)** [UMW*] | No | See the specific _DOS / PC_ section elsewhere in this guide | @@ -3021,7 +3034,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed | lutris | Lutris Open Gaming Platform | Lutris application **(Standalone)** [U] | | No | See the specific _Lutris_ section elsewhere in this guide | | lutro | Lutro Game Engine | Lutro | | | | | macintosh | Apple Macintosh | Basilisk II **(Standalone)** [UMW*] | SheepShaver **(Standalone)** [UMW*] | Yes | See the specific _Apple Macintosh_ section elsewhere in this guide | -| mame | Multiple Arcade Machine Emulator | MAME - Current | MAME 2010,
MAME 2003-Plus,
MAME 2000,
MAME **(Standalone)** [UMW*],
FinalBurn Neo,
FB Alpha 2012,
Flycast,
Flycast **(Standalone)** [UMW*],
Kronos [UW],
Model 2 Emulator **(Standalone)** [W*],
Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],
Supermodel **(Standalone)** [W*] | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | +| mame | Multiple Arcade Machine Emulator | MAME - Current | MAME 2010,
MAME 2003-Plus,
MAME 2000,
MAME **(Standalone)** [UMW*],
FinalBurn Neo,
FB Alpha 2012,
Flycast,
Flycast **(Standalone)** [UMW*],
Kronos [UW],
Model 2 Emulator **(Standalone)** [W*],
Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],
Supermodel **(Standalone)** [UW*] | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | mame-advmame | AdvanceMAME | _Placeholder_ | | Depends | | | mame-mame4all | MAME4ALL | _Placeholder_ | | Depends | | | mastersystem | Sega Master System | Genesis Plus GX | Genesis Plus GX Wide,
SMS Plus GX,
Gearsystem,
PicoDrive,
Mednafen **(Standalone)** [UMW*],
ares **(Standalone)** [UMW*] | No | Single archive or ROM file in root folder | @@ -3030,8 +3043,8 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed | megadrive | Sega Mega Drive | Genesis Plus GX | Genesis Plus GX Wide,
PicoDrive,
BlastEm,
BlastEm **(Standalone)** [U],
Mednafen **(Standalone)** [UMW*],
ares **(Standalone)** [UMW*] | No | Single archive or ROM file in root folder | | megaduck | Creatronic Mega Duck | SameDuck | | No | Single archive or ROM file in root folder | | mess | Multi Emulator Super System | MESS 2015 | | | | -| model2 | Sega Model 2 | Model 2 Emulator **(Standalone)** [W*] | Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*] | | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | -| model3 | Sega Model 3 | Supermodel **(Standalone)** [W*] | | | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | +| model2 | Sega Model 2 | Model 2 Emulator **(Standalone)** [W*],
MAME - Current [UM] | Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],
MAME - Current [W],
MAME **(Standalone)** [UMW*] | Yes for MAME | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | +| model3 | Sega Model 3 | Supermodel **(Standalone)** [UW*] | | No | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | moonlight | Moonlight Game Streaming | _Placeholder_ | | | | | moto | Thomson MO/TO Series | Theodore | | | | | msx | MSX | blueMSX | fMSX,
openMSX **(Standalone)** [UMW*],
openMSX No Machine **(Standalone)** [UMW*],
ares **(Standalone)** [UMW*] | Yes | | @@ -3065,7 +3078,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed | pico8 | PICO-8 Fantasy Console | PICO-8 **(Standalone)** | PICO-8 Splore **(Standalone)** | No | See the specific _PICO-8_ section elsewhere in this guide | | pokemini | Nintendo Pokémon Mini | PokeMini | | No | | | ports | Ports | _Various_ | | No | See the specific _Ports and desktop applications_ section elsewhere in this guide | -| ps2 | Sony PlayStation 2 | PCSX2 [UW],
PCSX2 **(Standalone)** [M] | PCSX2 **(Standalone)** [UW*],
PCSX2 Legacy **(Standalone)**@,
Play! **(Standalone)** [UMW*],
AetherSX2 **(Standalone)** [M] | Yes (No for Play!) | | +| ps2 | Sony PlayStation 2 | PCSX2 [UW],
PCSX2 **(Standalone)** [M] | PCSX2 **(Standalone)** [UW*],
PCSX2 Legacy **(Standalone)**@,
Play! **(Standalone)** [UMW*],
AetherSX2 **(Standalone)** [M] | Yes except for Play! | | | ps3 | Sony PlayStation 3 | RPCS3 Shortcut **(Standalone)** [UMW*] | RPCS3 Directory **(Standalone)** [UMW*] | Yes | See the specific _Sony PlayStation 3_ section elsewhere in this guide | | ps4 | Sony PlayStation 4 | _Placeholder_ | | | | | psp | Sony PlayStation Portable | PPSSPP | PPSSPP **(Standalone)** | No | Single .iso file in root folder | @@ -3097,7 +3110,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed | tanodragon | Tano Dragon | XRoar **(Standalone)** | | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide | | tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,
Mednafen **(Standalone)** [UMW*],
ares **(Standalone)** [UMW*] | No | Single archive or ROM file in root folder | | tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,
Mednafen **(Standalone)** [UMW*],
ares **(Standalone)** [UMW*] | Yes | | -| ti99 | Texas Instruments TI-99 | MAME **(Standalone)** [UW*] | | Yes | See the specific _Texas Instruments TI-99_ section elsewhere in this guide | +| ti99 | Texas Instruments TI-99 | MAME **(Standalone)** [UMW*] | | Yes | See the specific _Texas Instruments TI-99_ section elsewhere in this guide | | tic80 | TIC-80 Game Engine | TIC-80 | | No | Single .tic file in root folder | | to8 | Thomson TO8 | Theodore | | | | | trs-80 | Tandy TRS-80 | sdl2trs DOS Diskette **(Standalone)** [UW*] | sdl2trs Bootable Diskette **(Standalone)** [UW*],
sdl2trs CMD File **(Standalone)** [UW*] | Yes | See the specific _Tandy TRS-80_ section elsewhere in this guide | diff --git a/USERGUIDE.md b/USERGUIDE.md index 6c8a85fc7..21489e2a4 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -912,6 +912,12 @@ The EMULATOR.INI file is found in the _Model 2 Emulator_ installation directory. Also note that Model 2 Emulator is a bit broken and on most GPU drivers it will only work correctly if ES-DE keeps running in the background while the game is launched. However, for some GPU drivers the opposite is true and the emulator will only work if ES-DE is suspended. To use the latter setup, switch to the alternative emulator entry _Model 2 Emulator [Suspend ES-DE] (Standalone)_. +If using the Homebrew release of MAME standalone on macOS and emulating MESS systems like astrocde and ti99, then you need to configure the path to the MAME hash files in the mame.ini file. Alternatively you can symlink the installed hash directory to `~/.mame/` like the following (you will of course need to modify the command depending on which MAME version you have installed): +``` +ln -s /opt/homebrew/Cellar/mame/0.248/share/mame/hash ~/.mame/ # on ARM/Apple +ln -s /usr/local/Cellar/mame/0.248/share/mame/hash ~/.mame/ # on x86/Intel +``` + #### Nintendo Switch The Nintendo Switch emulator Yuzu is distributed as a Snap package, Flatpak package or AppImage on Linux and as a regular installer on Windows. At the moment there is unfortunately no macOS release of this emulator and it's unclear if it can run on BSD Unix. @@ -3025,7 +3031,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed | tanodragon | Tano Dragon | XRoar **(Standalone)** | | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide | | tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,
Mednafen **(Standalone)** [UMW*] | No | Single archive or ROM file in root folder | | tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,
Mednafen **(Standalone)** [UMW*] | Yes | | -| ti99 | Texas Instruments TI-99 | MAME **(Standalone)** [UW*] | | Yes | See the specific _Texas Instruments TI-99_ section elsewhere in this guide | +| ti99 | Texas Instruments TI-99 | MAME **(Standalone)** [UMW*] | | Yes | See the specific _Texas Instruments TI-99_ section elsewhere in this guide | | tic80 | TIC-80 Game Engine | TIC-80 | | No | Single .tic file in root folder | | to8 | Thomson TO8 | Theodore | | | | | trs-80 | Tandy TRS-80 | sdl2trs DOS Diskette **(Standalone)** [UW*] | sdl2trs Bootable Diskette **(Standalone)** [UW*],
sdl2trs CMD File **(Standalone)** [UW*] | Yes | See the specific _Tandy TRS-80_ section elsewhere in this guide | From 6599c125547eb920c5320d735ce8a2cce1634ac4 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 16 Oct 2022 13:51:22 +0200 Subject: [PATCH 32/56] Fixed an issue where ScrollableContainer would sometimes fade in text that had not been scrolled. --- es-core/src/components/ScrollableContainer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index dec2586ed..d8e4fc932 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -118,7 +118,7 @@ void ScrollableContainer::update(int deltaTime) if (!isVisible() || mSize == glm::vec2 {0.0f, 0.0f}) return; - const glm::vec2 contentSize {mChildren.front()->getSize()}; + const glm::vec2 contentSize {glm::round(mChildren.front()->getSize())}; float rowModifier {1.0f}; float lineSpacing {mChildren.front()->getLineSpacing()}; From 2f5bc97d4c44c2c32097f6859adde76b672dd325 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 16 Oct 2022 13:54:19 +0200 Subject: [PATCH 33/56] Documentation update. --- CONTRIBUTING.md | 4 ++-- USERGUIDE-DEV.md | 23 ++++++++++++++++++----- USERGUIDE.md | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eec766b98..281b1e6ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,10 +67,10 @@ The roadmap is under constant review so expect it to change from time to time. S #### v2.0 (in progress) * New theme engine with generalized views (only System and Gamelist) and theme variants support -* Multiple new gamelist components (more carousel modes, grid component etc.) +* Multiple new components (carousel support for the Gamelist view, grid component etc.) * Lottie animation (vector graphics) and GIF animation support * OpenGL ES 3.0 renderer for use on the Raspberry Pi -* Replace the OpenGL fixed function pipeline with a shader-based renderer +* Replace the OpenGL fixed function pipeline renderer with a shader-based renderer * Improve text and font functions, e.g. dynamic texture allocation and faster and cleaner text wrapping * Improve the performance of the GLSL shader post-processing diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 30437cbab..f376e30ab 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -664,7 +664,7 @@ RetroArch does not embed any version information into the filename so no wildcar ## Using manually downloaded emulators on Linux -Normally on Linux you would install emulators using either one of the established package formats, i.e. Flatpak, AppImage or Snap, or you would install them using the operating system repository. Less likely would be to build from source code and install to a standard system directory. In all these instances ES-DE should be able to find the emulator when launching a game. But in some rare cases you may instead manually download an emulator as an archive file to unzip somewhere on the file system. Normally you would want to place these files in your home directory, and if running a distribution that has an immutable filesystem (such as SteamOS), you don't even have the choice to install them to a standard system directory. +Normally on Linux you would install emulators using either one of the established cross-distribution package formats, i.e. AppImage, Snap or Flatpak, or you would install them using the operating system repository (including the AUR if available on your OS). Less likely would be to manually build from source code and install to a standard system directory. In all these instances ES-DE should be able to find the emulator when launching a game. But in some rare cases you may instead manually download an emulator as an archive file to unzip somewhere on the file system. Normally you would want to place these files in your home directory, and if running a distribution that has an immutable filesystem (such as SteamOS or Fedora Silverblue), you don't even have the choice to install them to a standard system directory. For these situations ES-DE looks for emulators in the same directories where it looks for AppImages (as explained in the section above), meaning: ``` @@ -909,13 +909,19 @@ Not all systems are as simple as described above, or there may be multiple ways #### Arcade and Neo Geo +**General** + For all the supported MAME variants as well as Final Burn Alpha/FinalBurn Neo and Neo Geo, single file archives should be used. But these should retain the MAME names as filenames since ES-DE ships with MAME lookup tables, meaning the MAME names are expanded to the full game names. For instance `topgunnr.7z` will be expanded to `Top Gunner`. This is required by the TheGamesDB scraper where the expanded filenames are used for game searches. (Screenscraper natively supports searches using the MAME names). It's also quite nice to have the gamelist populated with the expanded game names even before any scraping has taken place. -By default ES-DE will filter out BIOSes and devices that can't be launched directly, meaning these will never show up in the gamelist. But this only applies to files that are listed in the regular MAME driver file and BIOSes and devices for systems like MESS and Model 2 will not be filtered out. You'll instead need to manually hide these files using the _Hidden_ option in the metadata editor. +By default ES-DE will filter out BIOSes and devices that can't be launched directly, meaning these will never show up in the gamelist. But this only applies to files that are listed in the regular MAME driver file and BIOSes and devices for systems like MESS will not be filtered out. You'll instead need to manually hide these files using the _Hidden_ option in the metadata editor. + +If using the standalone release of FinalBurn Neo you also need to define the ROM directory in the fbneo.ini file or via the user interface as this emulator does not support passing the full path to the game ROM on game launch (see the comments about Model 2 Emulator below for more details). + +**Sega Model 2** If emulating Sega Model 2 games using _Model 2 Emulator_ on Windows, then you need to change the ROM directory path in the EMULATOR.INI file to point to your Model 2 ROMs. If you're using a portable ES-DE installation, then you can set the ROM directory path to be relative, for example: ``` @@ -927,14 +933,21 @@ The EMULATOR.INI file is found in the _Model 2 Emulator_ installation directory. Also note that Model 2 Emulator is a bit broken and on most GPU drivers it will only work correctly if ES-DE keeps running in the background while the game is launched. However, for some GPU drivers the opposite is true and the emulator will only work if ES-DE is suspended. To use the latter setup, switch to the alternative emulator entry _Model 2 Emulator [Suspend ES-DE] (Standalone)_. -Likewise, if using the standalone release of FinalBurn Neo you also need to define the ROM directory in the fbneo.ini file or via the user interface as this emulator does not support passing the full path to the game ROM on game launch. +On Unix/Linux and macOS, the only available emulator for Sega Model 2 is MAME, either the RetroArch - Current core or MAME standalone. Compatibility is still quite poor with only a handful of games working correctly, but this is likely to improve going forward as almost all games for this platform can already start and run to a certain degree. Some games flagged as not working by MAME are still playable with only minor glitches to audio and graphics, just make sure to use a recent ROM set for maximum compatibility. -On Unix/Linux and macOS, the only available emulator for Sega Model 2 is MAME, either the RetroArch - Current core or MAME standalone. Compatibility is still quite poor with only a handful of games working correctly, but this is likely to improve going forward as almost all games for this platform can already start and run to a certain degree. Some games flagged as not working by MAME are still playable with only minor glitches to audio and graphics, but make sure to use a recent ROM set for maximum compatibility. +**Sega Model 3** -For Sega Model 3 emulation on Linux download the custom [Supermodel_2022-10-07.tar.gz](https://gitlab.com/es-de/emulationstation-de/-/package_files/55835402/download) package for ES-DE and follow the instructions in the Readme.txt file. In summary you need to place the `supermodel` binary into `~/Applications/Supermodel/` and you need to place the `Config` and `NVRAM` directories into `~/ROMs/model3/`. Note that this build does not include network support as that would make it incompatible with SteamOS. Apart from that it runs really well. If you're using a Linux OS with access to the AUR, then you can use that release of Supermodel instead. But if you do, you still need to place your Config and NVRAM directories into ~/ROMs/model3/ so it's a good idea to download the custom build and read the Readme.txt file to fully understand the required setup. +For Sega Model 3 emulation on Linux download the custom [Supermodel_2022-10-07.tar.gz](https://gitlab.com/es-de/emulationstation-de/-/package_files/55835402/download) package for ES-DE and follow the instructions in the Readme.txt file. In summary you need to place the `supermodel` binary into `~/Applications/Supermodel/` and you need to place the `Config` and `NVRAM` directories into `~/ROMs/model3/`. Note that this build does not include network support as that would make it incompatible with SteamOS. Apart from that it runs really well. If you're using a Linux OS with access to the AUR, then you can use that release of Supermodel instead. But if you do, you still need to place your Config and NVRAM directories into ~/ROMs/model3/ so it's a good idea to download the custom package linked above and read the Readme.txt file to fully understand the required setup. Although there is a Homebrew release of Supermodel for macOS this seems to be quite old and is apparently not working correctly so for the time being the model3 system is unsupported on this operating system. +It's possible to add per-game command line parameters that will be passed to Supermodel on launch. To accomplish this, create a file named _\.commands_ in the same directory as the game file, for example `daytona2.commands` and add the options to this file. Here is an example of what the file contents could look like: +``` +-legacy3d -show-fps +``` + +**MAME standalone on macOS** + If using the Homebrew release of MAME standalone on macOS and emulating MESS systems like astrocde and ti99, then you need to configure the path to the MAME hash files in the mame.ini file. Alternatively you can symlink the installed hash directory to `~/.mame/` like the following (you will of course need to modify the command depending on which MAME version you have installed): ``` ln -s /opt/homebrew/Cellar/mame/0.248/share/mame/hash ~/.mame/ # on ARM/Apple diff --git a/USERGUIDE.md b/USERGUIDE.md index 21489e2a4..bf64cece2 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -659,7 +659,7 @@ RetroArch does not embed any version information into the filename so no wildcar ## Using manually downloaded emulators on Linux -Normally on Linux you would install emulators using either one of the established package formats, i.e. Flatpak, AppImage or Snap, or you would install them using the operating system repository. Less likely would be to build from source code and install to a standard system directory. In all these instances ES-DE should be able to find the emulator when launching a game. But in some rare cases you may instead manually download an emulator as an archive file to unzip somewhere on the file system. Normally you would want to place these files in your home directory, and if running a distribution that has an immutable filesystem (such as SteamOS), you don't even have the choice to install them to a standard system directory. +Normally on Linux you would install emulators using either one of the established cross-distribution package formats, i.e. AppImage, Snap or Flatpak, or you would install them using the operating system repository (including the AUR if available on your OS). Less likely would be to manually build from source code and install to a standard system directory. In all these instances ES-DE should be able to find the emulator when launching a game. But in some rare cases you may instead manually download an emulator as an archive file to unzip somewhere on the file system. Normally you would want to place these files in your home directory, and if running a distribution that has an immutable filesystem (such as SteamOS or Fedora Silverblue), you don't even have the choice to install them to a standard system directory. For these situations ES-DE looks for emulators in the same directories where it looks for AppImages (as explained in the section above), meaning: ``` From 0f327582c1c0a2b3ba425235bb857e25f5ac7fef Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 16 Oct 2022 15:00:38 +0200 Subject: [PATCH 34/56] Fixed an issue where ScrollableContainer would reset too early. --- es-core/src/components/ScrollableContainer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index d8e4fc932..1199fb11c 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -188,8 +188,8 @@ void ScrollableContainer::update(int deltaTime) if (mScrollPos.y < 0.0f) mScrollPos.y = 0.0f; - if (mScrollPos.x + mSize.x > contentSize.x) { - mScrollPos.x = contentSize.x - mSize.x; + if (mScrollPos.x + std::round(mSize.x) > contentSize.x) { + mScrollPos.x = contentSize.x - std::round(mSize.x); mAtEnd = true; } From 45fa3d2c372736b698d657d2c28988c7c08a4757 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 16 Oct 2022 16:40:52 +0200 Subject: [PATCH 35/56] Added a pillarboxThreshold property to control these values for the video element. --- es-core/src/ThemeData.cpp | 1 + es-core/src/components/VideoComponent.cpp | 7 ++++++ es-core/src/components/VideoComponent.h | 1 + .../src/components/VideoFFmpegComponent.cpp | 24 ++++++++++++------- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 2a1c9c48c..495d03516 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -123,6 +123,7 @@ std::map> {"audio", BOOLEAN}, {"interpolation", STRING}, {"pillarboxes", BOOLEAN}, + {"pillarboxThreshold", NORMALIZED_PAIR}, {"scanlines", BOOLEAN}, {"delay", FLOAT}, {"fadeInTime", FLOAT}, diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index d0627e166..4cb76b6c5 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -25,6 +25,7 @@ VideoComponent::VideoComponent() , mTargetSize {0.0f, 0.0f} , mVideoAreaPos {0.0f, 0.0f} , mVideoAreaSize {0.0f, 0.0f} + , mPillarboxThreshold {0.85f, 0.90f} , mStartTime {0} , mIsPlaying {false} , mIsActuallyPlaying {false} @@ -256,6 +257,12 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("pillarboxes")) mDrawPillarboxes = elem->get("pillarboxes"); + if (elem->has("pillarboxThreshold")) { + const glm::vec2 pillarboxThreshold {elem->get("pillarboxThreshold")}; + mPillarboxThreshold.x = glm::clamp(pillarboxThreshold.x, 0.2f, 1.0f); + mPillarboxThreshold.y = glm::clamp(pillarboxThreshold.y, 0.2f, 1.0f); + } + if (elem->has("scanlines")) mRenderScanlines = elem->get("scanlines"); diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index d5dddf14d..8072930a0 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -110,6 +110,7 @@ protected: glm::vec2 mTargetSize; glm::vec2 mVideoAreaPos; glm::vec2 mVideoAreaSize; + glm::vec2 mPillarboxThreshold; std::shared_ptr mTexture; std::string mStaticImagePath; std::string mDefaultImagePath; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index 2d9960ce7..fa318a70f 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -103,7 +103,6 @@ void VideoFFmpegComponent::resize() mSize.y *= resizeScale.y; } - mSize.y = std::round(mSize.y); mSize.x = (mSize.y / textureSize.y) * textureSize.x; } else { @@ -113,11 +112,11 @@ void VideoFFmpegComponent::resize() // If only one component is set, we resize in a way that maintains aspect ratio. if (!mTargetSize.x && mTargetSize.y) { - mSize.y = std::round(mTargetSize.y); + mSize.y = mTargetSize.y; mSize.x = (mSize.y / textureSize.y) * textureSize.x; } else if (mTargetSize.x && !mTargetSize.y) { - mSize.y = std::round((mTargetSize.x / textureSize.x) * textureSize.y); + mSize.y = (mTargetSize.x / textureSize.x) * textureSize.y; mSize.x = (mSize.y / textureSize.y) * textureSize.x; } } @@ -933,6 +932,11 @@ void VideoFFmpegComponent::calculateBlackRectangle() // otherwise it will exactly match the video size. The reason to add a black rectangle // behind videos in this second instance is that the scanline rendering will make the // video partially transparent so this may avoid some unforseen issues with some themes. + // In general, adding very narrow pillarboxes or letterboxes doesn't look good, so by + // default this is not done unless the size of the video vs the overall video area is + // above the threshold defined by mPillarboxThreshold. By default this is set to 0.85 + // for the X axis and 0.90 for the Y axis, but this is theme-controllable via the + // pillarboxThreshold property. if (mVideoAreaPos != glm::vec2 {0.0f, 0.0f} && mVideoAreaSize != glm::vec2 {0.0f, 0.0f}) { mVideoRectangleCoords.clear(); mRectangleOffset = {0.0f, 0.0f}; @@ -949,20 +953,24 @@ void VideoFFmpegComponent::calculateBlackRectangle() // and then scaled, there could be rounding errors that make the video height // slightly higher than allowed. It's only a single pixel or a few pixels, but // it's still visible for some videos. - if (mSize.y < mVideoAreaSize.y && mSize.y / mVideoAreaSize.y < 0.90f) + if (mSize.y < mVideoAreaSize.y && + mSize.y / mVideoAreaSize.y < mPillarboxThreshold.y) rectHeight = mVideoAreaSize.y; else rectHeight = mSize.y; - // Don't add a black border that is too narrow, that's what the 0.85 constant - // takes care of. - if (mSize.x < mVideoAreaSize.x && mSize.x / mVideoAreaSize.x < 0.85f) + if (mSize.x < mVideoAreaSize.x && + mSize.x / mVideoAreaSize.x < mPillarboxThreshold.x) rectWidth = mVideoAreaSize.x; else rectWidth = mSize.x; } // Video is in portrait orientation (or completely square). else { - rectWidth = mVideoAreaSize.x; + if (mSize.x <= mVideoAreaSize.x && + mSize.x / mVideoAreaSize.x < mPillarboxThreshold.x) + rectWidth = mVideoAreaSize.x; + else + rectWidth = mSize.x; rectHeight = mSize.y; } // If an origin value other than 0.5 is used, then create an offset for centering From dce7973fe163bdcffcca9059c68d95e15320853d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 16 Oct 2022 16:43:27 +0200 Subject: [PATCH 36/56] Documentation update. --- CHANGELOG.md | 1 + THEMES-DEV.md | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0de945c3d..abe8f004a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ * Added theme support for defining color saturation for images, videos and animations * Added theme support for defining the video fade-in time * Added theme support for enabling and disabling video pillarboxes and scanline rendering +* Added theme support for defining the threshold for when pillarboxes should be applied to a video * Added theme support for enabling or disabling audio playback for videos * Added theme support for setting separate textColorDimmed and iconColorDimmed properties for the system and gamelist views * Added support for nesting of theme variables diff --git a/THEMES-DEV.md b/THEMES-DEV.md index d75c2b5eb..5c44829ed 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -915,6 +915,10 @@ Properties: * `pillarboxes` - type: BOOLEAN - Whether to render black pillarboxes (and to a lesses extent letterboxes) for videos with aspect ratios where this is applicable. This is for instance useful for arcade game videos in vertical orientation. - Default is `true` +* `pillarboxThreshold` - type: NORMALIZED_PAIR + - Normally it doesn't look very good to add really narrow pillarboxes or letterboxes, so by default they are skipped if the actual video size is not reaching a threshold value as compared to the overall defined video area size. By modifying this property it's possible to control that threshold, as for some theme designs it will look better with the consistency of always rendering the pillarboxes/letterboxes even if they are narrow. To clarify, the default X axis value of 0.85 means that if the video width is 85% or less as compared to the X axis defined by the `size` property, then pillarboxes will be rendered. So setting the `pillarboxThreshold` value to `1 1` will always apply pillarboxes/letterboxes regardless of the video file dimension. + - Minimum value per axis is `0.2` and maximum value per axis is `1` + - Default is `0.85 0.90` * `scanlines` - type: BOOLEAN - Whether to use a shader to render scanlines. - Default is `false` From 1150b4692ed0759e95373fa7edb7163b89a8af69 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 16 Oct 2022 16:44:55 +0200 Subject: [PATCH 37/56] (slate-DE) Updated for the latest theme engine functionality. --- themes/slate-DE/theme.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/themes/slate-DE/theme.xml b/themes/slate-DE/theme.xml index 1b384ac66..7493961cd 100644 --- a/themes/slate-DE/theme.xml +++ b/themes/slate-DE/theme.xml @@ -328,6 +328,8 @@ 0.5 0.5 image nearest + true + 0.85 0.90 1.7 true From d2e5dbf49a07fdabcf8b6af110d3dfd1a7921e36 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 16 Oct 2022 22:57:20 +0200 Subject: [PATCH 38/56] Added the .car and .rom extensions to the a5200 system and the .car extension to the atari800 system. --- resources/systems/macos/es_systems.xml | 4 ++-- resources/systems/unix/es_systems.xml | 4 ++-- resources/systems/windows/es_systems.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/systems/macos/es_systems.xml b/resources/systems/macos/es_systems.xml index 81a371f23..6a62a91fc 100644 --- a/resources/systems/macos/es_systems.xml +++ b/resources/systems/macos/es_systems.xml @@ -140,7 +140,7 @@ atari5200 Atari 5200 %ROMPATH%/atari5200 - .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .cas .CAS .cdm .CDM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP + .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .car .CAR .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/a5200_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/atari800_libretro.dylib %ROM% %EMULATOR_ATARI800% %ROM% @@ -160,7 +160,7 @@ atari800 Atari 800 %ROMPATH%/atari800 - .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP + .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .car .CAR .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/atari800_libretro.dylib %ROM% %EMULATOR_ATARI800% %ROM% atari800 diff --git a/resources/systems/unix/es_systems.xml b/resources/systems/unix/es_systems.xml index 3ea95dbfe..04e5ee959 100644 --- a/resources/systems/unix/es_systems.xml +++ b/resources/systems/unix/es_systems.xml @@ -143,7 +143,7 @@ atari5200 Atari 5200 %ROMPATH%/atari5200 - .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .cas .CAS .cdm .CDM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP + .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .car .CAR .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/a5200_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/atari800_libretro.so %ROM% %EMULATOR_ATARI800% %ROM% @@ -163,7 +163,7 @@ atari800 Atari 800 %ROMPATH%/atari800 - .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP + .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .car .CAR .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/atari800_libretro.so %ROM% %EMULATOR_ATARI800% %ROM% atari800 diff --git a/resources/systems/windows/es_systems.xml b/resources/systems/windows/es_systems.xml index c5799f763..c0a76c05c 100644 --- a/resources/systems/windows/es_systems.xml +++ b/resources/systems/windows/es_systems.xml @@ -145,7 +145,7 @@ atari5200 Atari 5200 %ROMPATH%\atari5200 - .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .cas .CAS .cdm .CDM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP + .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .car .CAR .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\a5200_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\atari800_libretro.dll %ROM% %STARTDIR%=%EMUDIR% %EMULATOR_ATARI800% %ROM% @@ -165,7 +165,7 @@ atari800 Atari 800 %ROMPATH%\atari800 - .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP + .a52 .A52 .atr .ATR .atx .ATX .bin .BIN .car .CAR .cas .CAS .cdm .CDM .rom .ROM .xex .XEX .xfd .XFD .7z .7Z .zip .ZIP %STARTDIR%=%EMUDIR% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\atari800_libretro.dll %ROM% %STARTDIR%=%EMUDIR% %EMULATOR_ATARI800% %ROM% atari800 From 103e59b54df761c91f7749aa252d72ff136658fc Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 17 Oct 2022 22:21:52 +0200 Subject: [PATCH 39/56] Fixed an issue where mutually exclusive system variables could lead to theme loading errors. --- es-app/src/SystemData.cpp | 12 +++++++++++- es-core/src/ThemeData.cpp | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index a33adf38c..1959b3282 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -1303,7 +1303,9 @@ void SystemData::loadTheme() } try { - // Build map with system variables for theme to use. + // Build a map with system variables for the theme to use. Assign a backspace character + // to the variables that are not applicable. This will be used in ThemeData to make sure + // unpopulated system variables do not lead to theme loading errors. std::map sysData; sysData.insert(std::pair("system.name", getName())); sysData.insert(std::pair("system.theme", getThemeFolder())); @@ -1315,6 +1317,10 @@ void SystemData::loadTheme() std::pair("system.fullName.collections", getFullName())); sysData.insert( std::pair("system.theme.collections", getThemeFolder())); + sysData.insert(std::pair("system.name.noCollections", "\b")); + sysData.insert( + std::pair("system.fullName.noCollections", "\b")); + sysData.insert(std::pair("system.theme.noCollections", "\b")); } else { sysData.insert( @@ -1323,6 +1329,10 @@ void SystemData::loadTheme() getFullName())); sysData.insert(std::pair("system.theme.noCollections", getThemeFolder())); + sysData.insert(std::pair("system.name.collections", "\b")); + sysData.insert( + std::pair("system.fullName.collections", "\b")); + sysData.insert(std::pair("system.theme.collections", "\b")); } mTheme->loadFile(sysData, path); diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 495d03516..bbf67443e 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -1205,6 +1205,14 @@ void ThemeData::parseElement(const pugi::xml_node& root, std::string str {resolvePlaceholders(node.text().as_string())}; + // Handle the special case with mutually exclusive system variables, for example + // system.fullName.collections and system.fullName.noCollections which can never + // exist at the same time. A backspace is assigned in SystemData to flag the + // variables that do not apply and if it's encountered here we simply skip the + // property. + if (!mLegacyTheme && str == "\b") + continue; + // Skip this check for legacy themes to not break backward compatibility with some // themes sets that include empty property values. if (!mLegacyTheme && str == "") From 4a41a3530be5d040b6f5614c176adfc15b12a3cc Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 17 Oct 2022 22:24:51 +0200 Subject: [PATCH 40/56] Documentation update. --- CHANGELOG.md | 2 ++ THEMES-DEV.md | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abe8f004a..4a15f4ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ * (Windows) Changed the binary for emulator Citra from citra.exe to citra-qt.exe as the command line binary is broken on this OS * Added CPCemu standalone as an alternative emulator for the amstradcpc system * Added MAME standalone as an alternative emulator for the gx4000 system +* Added the .car and .rom file extensions to the a5200 system +* Added the .car file extension to the atari800 system * Added the .bin file extension to the gx4000 system * Added the .m3u file extension to the pcfx system * Removed the .7z and .zip file extensions from the 3do, neogeocd and neogeocdjp systems diff --git a/THEMES-DEV.md b/THEMES-DEV.md index 5c44829ed..107a0fe60 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -699,13 +699,14 @@ The `helpsystem` element does not really have a zIndex value and is always rende ## Theme variables -Theme variables can be used to simplify theme construction. There are 2 types of variables available. +Theme variables can be used to simplify theme construction and there are two types available: + * System variables * Theme defined variables ### System variables -System variables are system specific and are derived from the values in es_systems.xml (except for collections). +System variables are system specific and are derived from the values defined in es_systems.xml (except for collections which are derived from hardcoded application-internal values). * `system.name` * `system.name.collections` * `system.name.noCollections` @@ -716,9 +717,35 @@ System variables are system specific and are derived from the values in es_syste * `system.theme.collections` * `system.theme.noCollections` +`system.name` expands to the short name of the system as defined by the `name` tag in es_systems.xml\ +`system.fullName` expands to the full system name as defined by the `fullname` tag in es_systems.xml\ +`system.theme` expands to the theme directory as defined by the `theme` tag in es_systems.xml + +The `.collections` and `.noCollections` versions of these variables make it possible to differentiate between regular systems and collections. This can for example be used to apply different formatting to the names of the collections as opposed to regular systems. The below example capitalizes the names of the collections while leaving the regular systems at their default formatting (as they are defined in es_systems.xml). The reason this works is that the .collections and .noCollections variables are mutually exclusive, i.e. they can never both hold a value at the same time as a system is either a real system or a collection and never both. + +```xml + + + 0.05 0.83 + 0.9 0.06 + 0.06 + ./core/font.ttf + + + capitalize + + + ${system.fullName.noCollections} + + + ${system.fullName.collections} + + +``` + ### Theme defined variables Variables can also be defined in the theme. -``` +```xml 8B0000 @@ -726,19 +753,19 @@ Variables can also be defined in the theme. ### Usage in themes Variables can be used to specify the value of a theme property: -``` +```xml ${themeColor} ``` It can also be used to specify only a portion of the value of a theme property: -``` +```xml ${themeColor}C0 ./core/images/${system.theme}.svg ```` Nesting of variables is supported, so the following could be done: -``` +```xml 8b0000 ${colorRed} From cbb62775ac229f229dfbe0af0472c68637874e91 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 18 Oct 2022 18:31:19 +0200 Subject: [PATCH 41/56] Documentation update. --- FAQ.md | 4 ++-- USERGUIDE-DEV.md | 22 ++++++++++++---------- USERGUIDE.md | 24 ++++++++++++++---------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/FAQ.md b/FAQ.md index 32d6f7c53..0a165b571 100644 --- a/FAQ.md +++ b/FAQ.md @@ -86,11 +86,11 @@ This release of RetroArch has multiple technical issues so it's not officially s ## How do I add more themes? -Most RetroPie EmulationStation theme sets will work with ES-DE, and there are numerous resources online on where to find these. How to install them is described in the _Themes_ section of the [User guide](USERGUIDE.md#themes). Just be aware that some of these themes do not include support for modern systems like PlayStation 3 and Nintendo Switch so those platforms may look a bit ugly depending on how the theme is written. +Refer to the official list of [recommended theme sets](https://gitlab.com/es-de/themes/themes-list) for a selection of high-quality themes. There are also some brief instructions there on how to download and install them. More comprehensive documentation is available in the _Themes_ section of the [User guide](USERGUIDE.md#themes). In addition to the recommended theme sets you'll be able to find a lot of additional themes if doing a web search as almost all RetroPie-compatible themes can be used with ES-DE. Just be aware that many of these do not include support for modern systems like PlayStation 3 and Nintendo Switch so those platforms may look a bit ugly depending on how the theme has been written. ## The themes I've added don't seem to work? -Only RetroPie EmulationStation themes are supported, you can't use themes that were specifically developed for Batocera or Recalbox EmulationStation. A very few RetroPie themes like es-theme-carbon-2021 will not work either due to technical reasons. +Only RetroPie EmulationStation themes are supported, you can't use themes that were specifically developed for Batocera or Recalbox EmulationStation. A very few RetroPie themes like es-theme-carbon-2021 will not work either due to technical reasons. Refer to the official list of [recommended theme sets](https://gitlab.com/es-de/themes/themes-list) for a selection of high-quality themes that have been thoroughly tested with ES-DE. ## I used to be a Batocera/Recalbox user and ES-DE can't seem to find some of my games? diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index f376e30ab..e38ac74bb 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -2907,31 +2907,33 @@ If you're migrating from a previous version of EmulationStation that has absolut ## Themes -ES-DE is fully themeable, and although the application ships with the comprehensive slate-DE and modern-DE theme sets, you can use most RetroPie-compatible EmulationStation themes as well. Just be aware that ES-DE has added additional theme functionality compared to the RetroPie fork and more still will be added in future versions. This means that you may not get the full benefits of the application if you're using a theme set which has not been updated specifically for ES-DE. Some themes may also look slightly different as bugs that were present in the RetroPie fork have been fixed. Also note that most Batocera and Recalbox themes are not compatible as these forks are quite different. +ES-DE is fully themeable, and although the application ships with the comprehensive slate-DE and modern-DE theme sets, you can use most RetroPie-compatible EmulationStation themes as well as any themes made specifically for the ES-DE 2.0 theme engine. Note that most Batocera and Recalbox themes are not compatible as these forks use a different theme engine than ES-DE. -As a side comment, the terms _theme_ and _theme set_ are both used when talking about theming. The technically correct term for what you apply to the application to achieve a different look is a _theme set_ as it's a collection of a number of themes for a number of game systems. But in practice it doesn't matter as both terms refer to the same thing and the terms are used interchangeably in this guide. +As a side comment, the terms _theme_ and _theme set_ are both used when talking about theming. The technically correct term for what you apply to the application to achieve a different look is a _theme set_ as it's a collection of a number of themes for a number of game systems. But in practice it doesn't matter as both terms refer to the same thing and the terms are used interchangeably in the ES-DE documentation. -Themes are most easily installed to your ES-DE home directory, i.e. `~/.emulationstation/themes`. By just adding the theme sets there, one folder each, they will be found during startup and you can then choose between them via the _UI Settings_ menu on the main menu. +Themes are most easily installed to your ES-DE home directory, i.e. `~/.emulationstation/themes/`. By just adding the theme sets there, one folder each, they will be found during startup and you can then choose between them via the _UI Settings_ menu on the main menu. If using the portable release of ES-DE on Windows, the .emulationstation folder can be found in the root of the EmulationStation-DE directory. -For this example, we've downloaded the [Carbon](https://github.com/RetroPie/es-theme-carbon) and [Fundamental](https://github.com/G-rila/es-theme-fundamental) themes and uncompressed them to the ES-DE home directory: +To download a theme from its GitHub page, press the green _Code_ button in the upper right corner and choose _Download ZIP_. The process is identical on GitLab, but this site uses a button with a download symbol instead of a green button. You then simply unpack the file into `~/.emulationstation/themes/` and restart ES-DE. + +For this example, we've downloaded the [alekfull-nx-es-de](https://github.com/anthonycaccese/alekfull-nx-es-de) and [es-theme-carbon](https://github.com/RetroPie/es-theme-carbon) themes and uncompressed them to the themes directory: ``` +~/.emulationstation/themes/alekfull-nx-es-de ~/.emulationstation/themes/es-theme-carbon -~/.emulationstation/themes/es-theme-fundamental ``` -We now have four entries in the Theme set selector in the UI settings menu, i.e. _slate-DE, modern-DE, es-theme-carbon_ and _es-theme-fundamental_. +We now have four entries in the _Theme set_ selector in the UI settings menu, i.e. _alekfull-nx-es-de, es-theme-carbon, modern-DE and slate-DE_. -Although you place additional themes in your ES-DE home directory, the default slate-DE and modern-DE themes are located in the installation folder. For example this could be `/usr/share/emulationstation/themes` or `/usr/local/share/emulationstation/themes` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes` on macOS or `C:\Program Files\EmulationStation-DE\themes` on Windows. +Although you should place additional themes in your ES-DE home directory, the default slate-DE and modern-DE themes are located in the installation folder as they come bundled with the application. For example this could be `/usr/share/emulationstation/themes/` or `/usr/local/share/emulationstation/themes/` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes/` on macOS or `C:\Program Files\EmulationStation-DE\themes\` on Windows. If using the portable ES-DE release on Windows, the themes folder will be located in the root of the EmulationStation-DE directory. Note that if using the AppImage release on Linux, then there is no installation folder as all files are contained inside the AppImage file. -So if you would like to customize the slate-DE or modern-DE theme sets, simply make a copy of their directories to ~/.emulationstation/themes and then those copies will take precedence over the ones in the application installation directory. +If you would like to customize the slate-DE or modern-DE theme sets, simply make a copy of their directories to `~/.emulationstation/themes/` and then those copies will take precedence over the ones in the application installation directory. -Here is a good resource with a list of themes (although you will have to search online for the download location for each theme set): +Refer to the official list of recommended theme sets for a selection of high-quality themes: -https://retropie.org.uk/docs/Themes +https://gitlab.com/es-de/themes/themes-list ![alt text](images/es-de_ui_theme_support.png "ES-DE Theme Support") _This is a screenshot of the modern-DE theme that is bundled with ES-DE (in addition to the default slate-DE theme)._ diff --git a/USERGUIDE.md b/USERGUIDE.md index bf64cece2..e224b3ab2 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -2817,29 +2817,33 @@ If you're migrating from a previous version of EmulationStation that has absolut ## Themes -ES-DE is fully themeable, and although the application ships with the comprehensive rbsimple-DE and modern-DE theme sets, you can use most RetroPie-compatible EmulationStation themes as well. Just be aware that ES-DE has added additional theme functionality compared to the RetroPie fork and more still will be added in future versions. This means that you may not get the full benefits of the application if you're using a theme set which has not been updated specifically for ES-DE. Some themes may also look slightly different as bugs that were present in the RetroPie fork have been fixed. Also note that most Batocera and Recalbox themes are not compatible as these forks are quite different. +ES-DE is fully themeable, and although the application ships with the comprehensive rbsimple-DE and modern-DE theme sets, you can use most RetroPie-compatible EmulationStation themes as well. Note that most Batocera and Recalbox themes are not compatible as these forks use a different theme engine than ES-DE. -As a side comment, the terms _theme_ and _theme set_ are both used when talking about theming. The technically correct term for what you apply to the application to achieve a different look is a _theme set_ as it's a collection of a number of themes for a number of game systems. But in practice it doesn't matter as both terms refer to the same thing and the terms are used interchangeably in this guide. +As a side comment, the terms _theme_ and _theme set_ are both used when talking about theming. The technically correct term for what you apply to the application to achieve a different look is a _theme set_ as it's a collection of a number of themes for a number of game systems. But in practice it doesn't matter as both terms refer to the same thing and the terms are used interchangeably in the ES-DE documentation. -Themes are most easily installed to your ES-DE home directory, i.e. `~/.emulationstation/themes`. By just adding the theme sets there, one folder each, they will be found during startup and you can then choose between them via the _UI Settings_ menu on the main menu. +Themes are most easily installed to your ES-DE home directory, i.e. `~/.emulationstation/themes/`. By just adding the theme sets there, one folder each, they will be found during startup and you can then choose between them via the _UI Settings_ menu on the main menu. If using the portable release of ES-DE on Windows, the .emulationstation folder can be found in the root of the EmulationStation-DE directory. -For this example, we've downloaded the [Carbon](https://github.com/RetroPie/es-theme-carbon) and [Fundamental](https://github.com/G-rila/es-theme-fundamental) themes and uncompressed them to the ES-DE home directory: +To download a theme from its GitHub page, press the green _Code_ button in the upper right corner and choose _Download ZIP_. The process is identical on GitLab, but this site uses a button with a download symbol instead of a green button. You then simply unpack the file into `~/.emulationstation/themes/` and restart ES-DE. + +For this example, we've downloaded the [alekfull-nx-retropie](https://github.com/anthonycaccese/alekfull-nx-retropie) and [es-theme-carbon](https://github.com/RetroPie/es-theme-carbon) themes and uncompressed them to the themes directory: ``` +~/.emulationstation/themes/alekfull-nx-retropie ~/.emulationstation/themes/es-theme-carbon -~/.emulationstation/themes/es-theme-fundamental ``` -We now have four entries in the Theme set selector in the UI settings menu, i.e. _rbsimple-DE, modern-DE, es-theme-carbon_ and _es-theme-fundamental_. +We now have four entries in the _Theme set_ selector in the UI settings menu, i.e. _alekfull-nx-retropie, es-theme-carbon, modern-DE and rbsimple-DE_. -Although you place additional themes in your ES-DE home directory, the default rbsimple-DE and modern-DE themes are located in the installation folder. For example this could be `/usr/share/emulationstation/themes` or `/usr/local/share/emulationstation/themes` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes` on macOS or `C:\Program Files\EmulationStation-DE\themes` on Windows. +Although you should place additional themes in your ES-DE home directory, the default rbsimple-DE and modern-DE themes are located in the installation folder as they come bundled with the application. For example this could be `/usr/share/emulationstation/themes/` or `/usr/local/share/emulationstation/themes/` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes/` on macOS or `C:\Program Files\EmulationStation-DE\themes\` on Windows. If using the portable ES-DE release on Windows, the themes folder will be located in the root of the EmulationStation-DE directory. -So if you would like to customize the rbsimple-DE or modern-DE theme sets, simply make a copy of their directories to ~/.emulationstation/themes and then those copies will take precedence over the ones in the application installation directory. +Note that if using the AppImage release on Linux, then there is no installation folder as all files are contained inside the AppImage file. -Here is a good resource with a list of themes (although you will have to search online for the download location for each theme set): +If you would like to customize the rbsimple-DE or modern-DE theme sets, simply make a copy of their directories to `~/.emulationstation/themes/` and then those copies will take precedence over the ones in the application installation directory. -https://retropie.org.uk/docs/Themes +Refer to the official list of recommended theme sets for a selection of high-quality themes: + +https://gitlab.com/es-de/themes/themes-list ![alt text](images/es-de_ui_theme_support.png "ES-DE Theme Support") _This is a screenshot of the modern-DE theme that is bundled with ES-DE (in addition to the default rbsimple-DE theme)._ From c5098a62d55297eaadd0c5b05f8204c85836bb9f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 00:19:02 +0200 Subject: [PATCH 42/56] Added some error checking when downloading thumbnails in the scraper GUI. --- es-app/src/guis/GuiScraperSearch.cpp | 4 ++-- es-app/src/guis/GuiScraperSingle.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 89b98f63a..f426ec1f6 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -554,7 +554,7 @@ void GuiScraperSearch::updateInfoPane() // Cache the thumbnail image in mScraperResults so that we don't need to download // it every time the list is scrolled back and forth. - if (mScraperResults[i].thumbnailImageData.size() > 0) { + if (mScraperResults[i].thumbnailImageData.size() > 350) { std::string content {mScraperResults[i].thumbnailImageData}; mResultThumbnail->setImage(content.data(), content.length()); mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed. @@ -837,7 +837,7 @@ void GuiScraperSearch::updateThumbnail() } // Activate the thumbnail in the GUI. std::string content {mScraperResults[mResultList->getCursorId()].thumbnailImageData}; - if (content.size() > 0) { + if (content.size() > 350) { mResultThumbnail->setImage(content.data(), content.length()); mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed. } diff --git a/es-app/src/guis/GuiScraperSingle.cpp b/es-app/src/guis/GuiScraperSingle.cpp index 786f8e40b..ecbdf789d 100644 --- a/es-app/src/guis/GuiScraperSingle.cpp +++ b/es-app/src/guis/GuiScraperSingle.cpp @@ -150,7 +150,7 @@ void GuiScraperSingle::onSizeChanged() mGrid.setColWidthPerc(1, 0.04f); mGrid.setSize(glm::round(mSize)); - mBackground.fitTo(mSize, glm::vec3 {}, glm::vec2 {-32.0f, -32.0f}); + mBackground.fitTo(mSize, glm::vec3 {0.0f, 0.0f, 0.0f}, glm::vec2 {-32.0f, -32.0f}); // Add some extra margins to the game name. const float newSizeX {mSize.x * 0.96f}; From 84f019680d101fab44f5ad5a7dc62ed53b181325 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 00:39:40 +0200 Subject: [PATCH 43/56] Greatly improved text sizing and rendering. --- es-core/src/GuiComponent.cpp | 2 +- es-core/src/GuiComponent.h | 2 + es-core/src/HelpStyle.cpp | 3 +- es-core/src/components/DateTimeComponent.cpp | 2 +- .../src/components/ScrollableContainer.cpp | 35 ++++--- es-core/src/components/TextComponent.cpp | 33 +++++-- es-core/src/components/TextComponent.h | 6 ++ .../components/primary/CarouselComponent.h | 34 +++---- .../components/primary/TextListComponent.h | 26 +++-- es-core/src/resources/Font.cpp | 99 ++++++++++++------- es-core/src/resources/Font.h | 45 +++++---- 11 files changed, 181 insertions(+), 106 deletions(-) diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index d66849627..93085c563 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -206,7 +206,7 @@ void GuiComponent::setDimming(float dimming) const glm::mat4& GuiComponent::getTransform() { mTransform = Renderer::getIdentity(); - mTransform = glm::translate(mTransform, glm::round(mPosition)); + mTransform = glm::translate(mTransform, mPosition); if (mScale != 1.0f) mTransform = glm::scale(mTransform, glm::vec3 {mScale}); diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 03dc787f9..d9f354317 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -160,6 +160,8 @@ public: mComponentThemeFlags ^= ComponentThemeFlags::METADATA_ELEMENT; } + virtual int getTextCacheGlyphHeight() { return 0; } + // Returns the center point of the image (takes origin into account). const glm::vec2 getCenter() const; diff --git a/es-core/src/HelpStyle.cpp b/es-core/src/HelpStyle.cpp index 885e59f7c..0f505e52f 100644 --- a/es-core/src/HelpStyle.cpp +++ b/es-core/src/HelpStyle.cpp @@ -24,7 +24,6 @@ HelpStyle::HelpStyle() , opacity {1.0f} , letterCase {"uppercase"} { - if (FONT_SIZE_SMALL != 0) font = Font::get(FONT_SIZE_SMALL); else @@ -61,7 +60,7 @@ void HelpStyle::applyTheme(const std::shared_ptr& theme, const std::s iconColorDimmed = iconColor; if (elem->has("fontPath") || elem->has("fontSize")) - font = Font::getFromTheme(elem, ThemeFlags::ALL, font); + font = Font::getFromTheme(elem, ThemeFlags::ALL, font, 0.0f, theme->isLegacyTheme()); if (elem->has("entrySpacing")) entrySpacing = glm::clamp(elem->get("entrySpacing"), 0.0f, 0.04f); diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index d9bfb6de6..ed4dfb27e 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -224,5 +224,5 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, if (properties & LINE_SPACING && elem->has("lineSpacing")) setLineSpacing(glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f)); - setFont(Font::getFromTheme(elem, properties, mFont, maxHeight)); + setFont(Font::getFromTheme(elem, properties, mFont, maxHeight, theme->isLegacyTheme())); } diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index 1199fb11c..74cbb7250 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -66,10 +66,15 @@ void ScrollableContainer::reset() mAutoScrollResetAccumulator = 0; mAutoScrollAccumulator = -mAutoScrollDelay + mAutoScrollSpeed; mAtEnd = false; - // This is needed to resize to the designated area when the backgrund image gets invalidated. + // This is needed to resize to the designated area when the background image gets invalidated. if (!mChildren.empty()) { - float combinedHeight { - mChildren.front()->getFont()->getHeight(mChildren.front()->getLineSpacing())}; + float combinedHeight {0.0f}; + const float cacheGlyphHeight { + static_cast(mChildren.front()->getTextCacheGlyphHeight())}; + if (cacheGlyphHeight > 0.0f) + combinedHeight = cacheGlyphHeight * mChildren.front()->getLineSpacing(); + else + return; if (mChildren.front()->getSize().y > mSize.y) { if (mVerticalSnap) { float numLines {std::floor(mSize.y / combinedHeight)}; @@ -121,8 +126,13 @@ void ScrollableContainer::update(int deltaTime) const glm::vec2 contentSize {glm::round(mChildren.front()->getSize())}; float rowModifier {1.0f}; - float lineSpacing {mChildren.front()->getLineSpacing()}; - float combinedHeight {mChildren.front()->getFont()->getHeight(lineSpacing)}; + const float lineSpacing {mChildren.front()->getLineSpacing()}; + float combinedHeight {0.0f}; + const float cacheGlyphHeight {static_cast(mChildren.front()->getTextCacheGlyphHeight())}; + if (cacheGlyphHeight > 0.0f) + combinedHeight = cacheGlyphHeight * lineSpacing; + else + return; // Calculate the spacing which will be used to clip the container. if (lineSpacing > 1.2f && mClipSpacing == 0.0f) { @@ -175,7 +185,7 @@ void ScrollableContainer::update(int deltaTime) mAutoScrollAccumulator += deltaTime; while (mAutoScrollAccumulator >= static_cast(rowModifier * static_cast(mAdjustedAutoScrollSpeed))) { - if (contentSize.y > mAdjustedHeight) + if (!mAtEnd && contentSize.y > mAdjustedHeight) mScrollPos += mScrollDir; mAutoScrollAccumulator -= static_cast(rowModifier * static_cast(mAdjustedAutoScrollSpeed)); @@ -193,13 +203,10 @@ void ScrollableContainer::update(int deltaTime) mAtEnd = true; } - if (contentSize.y < mAdjustedHeight) { + if (contentSize.y < mAdjustedHeight) mScrollPos.y = 0.0f; - } - else if (mScrollPos.y + mAdjustedHeight > contentSize.y) { - mScrollPos.y = contentSize.y - mAdjustedHeight; + else if (mScrollPos.y + mAdjustedHeight > contentSize.y) mAtEnd = true; - } if (mAtEnd) { mAutoScrollResetAccumulator += deltaTime; @@ -237,8 +244,8 @@ void ScrollableContainer::render(const glm::mat4& parentTrans) dimScaled.x = std::fabs(trans[3].x + mSize.x); dimScaled.y = std::fabs(trans[3].y + mAdjustedHeight); - glm::ivec2 clipDim {static_cast(ceilf(dimScaled.x - trans[3].x)), - static_cast(ceilf(dimScaled.y - trans[3].y))}; + glm::ivec2 clipDim {static_cast(dimScaled.x - trans[3].x), + static_cast(dimScaled.y - trans[3].y)}; // By effectively clipping the upper and lower boundaries of the container we mostly avoid // scrolling outside the vertical starting and ending positions. @@ -247,7 +254,7 @@ void ScrollableContainer::render(const glm::mat4& parentTrans) mRenderer->pushClipRect(clipPos, clipDim); - trans = glm::translate(trans, glm::round(-glm::vec3 {mScrollPos.x, mScrollPos.y, 0.0f})); + trans = glm::translate(trans, -glm::vec3 {mScrollPos.x, mScrollPos.y, 0.0f}); mRenderer->setMatrix(trans); if (Settings::getInstance()->getBool("DebugText")) diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index ea32207ff..687e758dd 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -30,6 +30,7 @@ TextComponent::TextComponent() , mNoTopMargin {false} , mSelectable {false} , mVerticalAutoSizing {false} + , mLegacyTheme {false} { } @@ -58,6 +59,7 @@ TextComponent::TextComponent(const std::string& text, , mNoTopMargin {false} , mSelectable {false} , mVerticalAutoSizing {false} + , mLegacyTheme {false} { setFont(font); setColor(color); @@ -180,11 +182,11 @@ void TextComponent::render(const glm::mat4& parentTrans) break; } case ALIGN_BOTTOM: { - yOff = (getSize().y - textSize.y); + yOff = mSize.y - textSize.y; break; } case ALIGN_CENTER: { - yOff = std::round((getSize().y - textSize.y) / 2.0f); + yOff = (mSize.y - textSize.y) / 2.0f; break; } default: { @@ -194,7 +196,7 @@ void TextComponent::render(const glm::mat4& parentTrans) } else { // If height is smaller than the font height, then always center vertically. - yOff = std::round((getSize().y - textSize.y) / 2.0f); + yOff = (mSize.y - textSize.y) / 2.0f; } // Draw the overall textbox area. If we're inside a scrollable container then this @@ -264,10 +266,23 @@ void TextComponent::onTextChanged() if (!mFont || text.empty() || mSize.x < 0.0f) return; - std::shared_ptr font {mFont}; - const float lineHeight {font->getHeight(mLineSpacing)}; - const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y > lineHeight}; + float lineHeight {0.0f}; const bool isScrollable {mParent && mParent->isScrollable()}; + std::shared_ptr font {mFont}; + + if (mLegacyTheme && !isScrollable && (mVerticalAutoSizing || mAutoCalcExtent.x)) { + // This is needed to retain a bug from the legacy theme engine where lineSpacing + // is not sized correctly when using automatic text element sizing. This is only + // applied to legacy themes for backward compatibility reasons. + font->useLegacyMaxGlyphHeight(); + lineHeight = font->getHeight(mLineSpacing); + } + else { + // Used to initialize all glyphs, which is needed to populate mMaxGlyphHeight. + lineHeight = mFont->loadGlyphs(text + "\n") * mLineSpacing; + } + + const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y > lineHeight}; if (isMultiline && !isScrollable) { const std::string wrappedText { @@ -284,7 +299,7 @@ void TextComponent::onTextChanged() } if (mAutoCalcExtent.y) - mSize.y = font->getTextSize().y; + mSize.y = mTextCache->metrics.size.y; if (mOpacity != 1.0f || mThemeOpacity != 1.0f) setOpacity(mOpacity); @@ -333,6 +348,8 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, using namespace ThemeFlags; GuiComponent::applyTheme(theme, view, element, properties); + mLegacyTheme = theme->isLegacyTheme(); + std::string elementType {"text"}; std::string componentName {"TextComponent"}; @@ -472,5 +489,5 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, if (properties & LINE_SPACING && elem->has("lineSpacing")) setLineSpacing(glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f)); - setFont(Font::getFromTheme(elem, properties, mFont, maxHeight)); + setFont(Font::getFromTheme(elem, properties, mFont, maxHeight, theme->isLegacyTheme())); } diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index a8a04be9b..d117f6a2a 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -78,6 +78,11 @@ public: Alignment getHorizontalAlignment() { return mHorizontalAlignment; } Alignment getVerticalAlignment() { return mVerticalAlignment; } + int getTextCacheGlyphHeight() override + { + return (mTextCache == nullptr ? 0 : mTextCache->metrics.maxGlyphHeight); + } + protected: virtual void onTextChanged(); @@ -114,6 +119,7 @@ private: bool mNoTopMargin; bool mSelectable; bool mVerticalAutoSizing; + bool mLegacyTheme; }; #endif // ES_CORE_COMPONENTS_TEXT_COMPONENT_H diff --git a/es-core/src/components/primary/CarouselComponent.h b/es-core/src/components/primary/CarouselComponent.h index 505c0e18d..6f3351918 100644 --- a/es-core/src/components/primary/CarouselComponent.h +++ b/es-core/src/components/primary/CarouselComponent.h @@ -183,8 +183,8 @@ CarouselComponent::CarouselComponent() , mMaxItemCount {3.0f} , mItemsBeforeCenter {8} , mItemsAfterCenter {8} - , mItemSize {glm::round( - glm::vec2 {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f})} + , mItemSize {glm::vec2 {Renderer::getScreenWidth() * 0.25f, + Renderer::getScreenHeight() * 0.155f}} , mLinearInterpolation {false} , mInstantItemTransitions {false} , mItemAxisHorizontal {false} @@ -568,11 +568,10 @@ template void CarouselComponent::render(const glm::mat4& parentT glm::mat4 carouselTrans {parentTrans}; carouselTrans = glm::translate( - carouselTrans, - glm::round(glm::vec3 {GuiComponent::mPosition.x, GuiComponent::mPosition.y, 0.0f})); - carouselTrans = glm::translate( - carouselTrans, glm::round(glm::vec3 {GuiComponent::mOrigin.x * mSize.x * -1.0f, - GuiComponent::mOrigin.y * mSize.y * -1.0f, 0.0f})); + carouselTrans, glm::vec3 {GuiComponent::mPosition.x, GuiComponent::mPosition.y, 0.0f}); + carouselTrans = + glm::translate(carouselTrans, glm::vec3 {GuiComponent::mOrigin.x * mSize.x * -1.0f, + GuiComponent::mOrigin.y * mSize.y * -1.0f, 0.0f}); if (carouselTrans[3].x + mSize.x <= 0.0f || carouselTrans[3].y + mSize.y <= 0.0f) return; @@ -650,8 +649,7 @@ template void CarouselComponent::render(const glm::mat4& parentT } } else if (mType == CarouselType::VERTICAL) { - itemSpacing.y = - std::round(((mSize.y - (mItemSize.y * mMaxItemCount)) / mMaxItemCount) + mItemSize.y); + itemSpacing.y = ((mSize.y - (mItemSize.y * mMaxItemCount)) / mMaxItemCount) + mItemSize.y; yOff = (mSize.y - mItemSize.y) / 2.0f - (camOffset * itemSpacing.y); if (mItemHorizontalAlignment == ALIGN_LEFT) { if (mLegacyMode) @@ -670,8 +668,7 @@ template void CarouselComponent::render(const glm::mat4& parentT } } else { // HORIZONTAL. - itemSpacing.x = - std::round(((mSize.x - (mItemSize.x * mMaxItemCount)) / mMaxItemCount) + mItemSize.x); + itemSpacing.x = ((mSize.x - (mItemSize.x * mMaxItemCount)) / mMaxItemCount) + mItemSize.x; xOff = (mSize.x - mItemSize.x) / 2.0f - (camOffset * itemSpacing.x); if (mItemVerticalAlignment == ALIGN_TOP) { if (mLegacyMode) @@ -780,9 +777,8 @@ template void CarouselComponent::render(const glm::mat4& parentT if (singleEntry) itemTrans = glm::translate(carouselTrans, glm::vec3 {xOff, yOff, 0.0f}); else - itemTrans = - glm::translate(itemTrans, glm::vec3 {std::round(i * itemSpacing.x + xOff), - std::round(i * itemSpacing.y + yOff), 0.0f}); + itemTrans = glm::translate(itemTrans, glm::vec3 {(i * itemSpacing.x) + xOff, + (i * itemSpacing.y) + yOff, 0.0f}); float opacity {0.0f}; @@ -1011,8 +1007,8 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("itemSize")) { const glm::vec2 itemSize {glm::clamp(elem->get("itemSize"), 0.05f, 1.0f)}; - mItemSize = glm::round( - itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())); + mItemSize = + itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight()); } if (elem->has("itemScale")) @@ -1164,8 +1160,8 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, itemSize.x = glm::clamp(itemSize.x, 0.005f, 1.0f); itemSize.y = glm::clamp(itemSize.y, 0.005f, 1.0f); } - mItemSize = glm::round( - itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())); + mItemSize = + itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight()); } if (elem->has("maxLogoCount")) { @@ -1210,7 +1206,7 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, } } - mFont = Font::getFromTheme(elem, properties, mFont); + mFont = Font::getFromTheme(elem, properties, mFont, 0.0f, mLegacyMode); if (elem->has("textColor")) mTextColor = elem->get("textColor"); diff --git a/es-core/src/components/primary/TextListComponent.h b/es-core/src/components/primary/TextListComponent.h index ad6e5b046..6cfebe848 100644 --- a/es-core/src/components/primary/TextListComponent.h +++ b/es-core/src/components/primary/TextListComponent.h @@ -350,10 +350,19 @@ template void TextListComponent::render(const glm::mat4& parentT int screenCount {0}; float y {0.0f}; - const float entrySize { - std::max(floorf(font->getHeight(1.0f)), floorf(static_cast(font->getSize()))) * - mLineSpacing}; - const float lineSpacingHeight {floorf(font->getHeight(mLineSpacing) - font->getHeight(1.0f))}; + float entrySize {0.0f}; + float lineSpacingHeight {0.0f}; + + // The vertical spacing between rows for legacy themes is very inaccurate and will look + // different depending on the resolution, but it's done for maximum backward compatibility. + if (mLegacyMode) { + entrySize = std::floor(font->getSize()) * mLineSpacing; + lineSpacingHeight = std::floor(font->getSize()) * mLineSpacing - font->getSize() * 1.0f; + } + else { + entrySize = font->getSize() * mLineSpacing; + lineSpacingHeight = font->getSize() * mLineSpacing - font->getSize() * 1.0f; + } if (mLegacyMode) { // This extra vertical margin is technically incorrect, but it adds a little extra leeway @@ -366,7 +375,9 @@ template void TextListComponent::render(const glm::mat4& parentT floorf((mSize.y + lineSpacingHeight / 2.0f + extraMargin) / entrySize)); } else { - screenCount = static_cast(floorf((mSize.y + lineSpacingHeight / 2.0f) / entrySize)); + // Number of entries that can fit on the screen simultaneously. + screenCount = + static_cast(std::floor((mSize.y + lineSpacingHeight / 2.0f) / entrySize)); } if (size() >= screenCount) { @@ -555,9 +566,8 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, setColor(1, elem->get("secondaryColor")); } - setFont(Font::getFromTheme(elem, properties, mFont)); - const float selectorHeight { - std::max(mFont->getHeight(1.0), static_cast(mFont->getSize())) * mLineSpacing}; + setFont(Font::getFromTheme(elem, properties, mFont, 0.0f, mLegacyMode)); + const float selectorHeight {mFont->getSize() * mLineSpacing}; setSelectorHeight(selectorHeight); if (properties & ALIGNMENT) { diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 8ea08cb42..b44d75a96 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -15,19 +15,20 @@ #include "utils/PlatformUtil.h" #include "utils/StringUtil.h" -Font::Font(int size, const std::string& path) +Font::Font(float size, const std::string& path) : mRenderer {Renderer::getInstance()} , mPath(path) - , mTextSize {0.0f, 0.0f} - , mFontSize(size) - , mMaxGlyphHeight {0} + , mFontSize {size} + , mLetterHeight {0.0f} + , mMaxGlyphHeight {static_cast(std::round(size))} + , mLegacyMaxGlyphHeight {0} { - if (mFontSize < 3) { - mFontSize = 3; + if (mFontSize < 3.0f) { + mFontSize = 3.0f; LOG(LogWarning) << "Requested font size too small, changing to minimum supported size"; } else if (mFontSize > Renderer::getScreenHeight() * 1.5f) { - mFontSize = static_cast(Renderer::getScreenHeight() * 1.5f); + mFontSize = Renderer::getScreenHeight() * 1.5f; LOG(LogWarning) << "Requested font size too large, changing to maximum supported size"; } @@ -35,7 +36,7 @@ Font::Font(int size, const std::string& path) initLibrary(); // Always initialize ASCII characters. - for (unsigned int i = 32; i < 128; ++i) + for (unsigned int i = 32; i < 127; ++i) getGlyph(i); clearFaceCache(); @@ -45,7 +46,7 @@ Font::~Font() { unload(ResourceManager::getInstance()); - auto fontEntry = sFontMap.find(std::pair(mPath, mFontSize)); + auto fontEntry = sFontMap.find(std::pair(mPath, mFontSize)); if (fontEntry != sFontMap.cend()) sFontMap.erase(fontEntry); @@ -56,11 +57,11 @@ Font::~Font() } } -std::shared_ptr Font::get(int size, const std::string& path) +std::shared_ptr Font::get(float size, const std::string& path) { const std::string canonicalPath {Utils::FileSystem::getCanonicalPath(path)}; - std::pair def {canonicalPath.empty() ? getDefaultPath() : canonicalPath, - size}; + const std::pair def { + canonicalPath.empty() ? getDefaultPath() : canonicalPath, size}; auto foundFont = sFontMap.find(def); if (foundFont != sFontMap.cend()) { @@ -76,11 +77,9 @@ std::shared_ptr Font::get(int size, const std::string& path) glm::vec2 Font::sizeText(std::string text, float lineSpacing) { + const float lineHeight {getHeight(lineSpacing)}; float lineWidth {0.0f}; float highestWidth {0.0f}; - - const float lineHeight {getHeight(lineSpacing)}; - float y {lineHeight}; size_t i {0}; @@ -106,6 +105,20 @@ glm::vec2 Font::sizeText(std::string text, float lineSpacing) return glm::vec2 {highestWidth, y}; } +int Font::loadGlyphs(const std::string& text) +{ + mMaxGlyphHeight = static_cast(std::round(mFontSize)); + + for (size_t i = 0; i < text.length();) { + unsigned int character {Utils::String::chars2Unicode(text, i)}; // Advances i. + Glyph* glyph {getGlyph(character)}; + + if (glyph->rows > mMaxGlyphHeight) + mMaxGlyphHeight = glyph->rows; + } + return mMaxGlyphHeight; +} + TextCache* Font::buildTextCache(const std::string& text, float offsetX, float offsetY, @@ -134,6 +147,9 @@ TextCache* Font::buildTextCache(const std::string& text, yBot = getHeight(1.5); } else { + // TODO: This is lacking some precision which is especially visible at higher resolutions + // like 4K where the text is not always placed entirely correctly vertically. Try to find + // a way to improve on this. yTop = getGlyph('S')->bearing.y; yBot = getHeight(lineSpacing); } @@ -203,7 +219,8 @@ TextCache* Font::buildTextCache(const std::string& text, TextCache* cache {new TextCache()}; cache->vertexLists.resize(vertMap.size()); - cache->metrics = {sizeText(text, lineSpacing)}; + cache->metrics.size = {sizeText(text, lineSpacing)}; + cache->metrics.maxGlyphHeight = mMaxGlyphHeight; size_t i {0}; for (auto it = vertMap.cbegin(); it != vertMap.cend(); ++it) { @@ -366,7 +383,6 @@ std::string Font::wrapText(const std::string& text, wrappedText.append("..."); } - mTextSize = {maxLength, accumHeight}; return wrappedText; } @@ -396,31 +412,36 @@ glm::vec2 Font::getWrappedTextCursorOffset(const std::string& wrappedText, float Font::getLetterHeight() { - Glyph* glyph {getGlyph('S')}; - assert(glyph); - return glyph->texSize.y * glyph->texture->textureSize.y; + if (mLetterHeight == 0.0f) + return mFontSize * 0.737; // Only needed if face does not contain the letter 'S'. + else + return mLetterHeight; } std::shared_ptr Font::getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, const std::shared_ptr& orig, - const float maxHeight) + const float maxHeight, + const bool legacyTheme) { + mLegacyTheme = legacyTheme; + using namespace ThemeFlags; if (!(properties & FONT_PATH) && !(properties & FONT_SIZE)) return orig; - int size {static_cast(orig ? orig->mFontSize : FONT_SIZE_MEDIUM)}; + float size {static_cast(orig ? orig->mFontSize : FONT_SIZE_MEDIUM)}; std::string path {orig ? orig->mPath : getDefaultPath()}; float screenHeight {static_cast(Renderer::getScreenHeight())}; - if (properties & FONT_SIZE && elem->has("fontSize")) - size = static_cast(glm::clamp(screenHeight * elem->get("fontSize"), - screenHeight * 0.001f, screenHeight * 1.5f)); + if (properties & FONT_SIZE && elem->has("fontSize")) { + size = glm::clamp(screenHeight * elem->get("fontSize"), screenHeight * 0.001f, + screenHeight * 1.5f); + } - if (maxHeight != 0.0f && static_cast(size) > maxHeight) - size = static_cast(maxHeight); + if (maxHeight != 0.0f && size > maxHeight) + size = maxHeight; if (properties & FONT_PATH && elem->has("fontPath")) path = elem->get("fontPath"); @@ -433,7 +454,10 @@ std::shared_ptr Font::getFromTheme(const ThemeData::ThemeElement* elem, path = getDefaultPath(); } - return get(size, path); + if (mLegacyTheme) + return get(std::floor(size), path); + else + return get(size, path); } size_t Font::getMemUsage() const @@ -558,7 +582,7 @@ void Font::FontTexture::deinitTexture() } } -Font::FontFace::FontFace(ResourceData&& d, int size, const std::string& path) +Font::FontFace::FontFace(ResourceData&& d, float size, const std::string& path) : data {d} { if (FT_New_Memory_Face(sLibrary, d.ptr.get(), static_cast(d.length), 0, &face) != 0) { @@ -566,7 +590,10 @@ Font::FontFace::FontFace(ResourceData&& d, int size, const std::string& path) Utils::Platform::emergencyShutdown(); } - FT_Set_Pixel_Sizes(face, 0, size); + // Even though a fractional font size can be requested, the glyphs will always be rounded + // to integers. It's not useless to call FT_Set_Char_Size() instead of FT_Set_Pixel_Sizes() + // though as the glyphs will still be much more evenely sized across different resolutions. + FT_Set_Char_Size(face, 0.0f, size * 64.0f, 0, 0); } Font::FontFace::~FontFace() @@ -712,6 +739,12 @@ Font::Glyph* Font::getGlyph(const unsigned int id) return nullptr; } + // Use the letter 'S' as a size reference. + if (mLetterHeight == 0 && id == 'S') { + const float ratio {static_cast(glyphSize.y) / std::round(mFontSize)}; + mLetterHeight = mFontSize * ratio; + } + // Create glyph. Glyph& glyph {mGlyphMap[id]}; @@ -720,18 +753,18 @@ Font::Glyph* Font::getGlyph(const unsigned int id) cursor.y / static_cast(tex->textureSize.y)}; glyph.texSize = glm::vec2 {glyphSize.x / static_cast(tex->textureSize.x), glyphSize.y / static_cast(tex->textureSize.y)}; - glyph.advance = glm::vec2 {static_cast(glyphSlot->metrics.horiAdvance) / 64.0f, static_cast(glyphSlot->metrics.vertAdvance) / 64.0f}; glyph.bearing = glm::vec2 {static_cast(glyphSlot->metrics.horiBearingX) / 64.0f, static_cast(glyphSlot->metrics.horiBearingY) / 64.0f}; + glyph.rows = glyphSlot->bitmap.rows; // Upload glyph bitmap to texture. mRenderer->updateTexture(tex->textureId, Renderer::TextureType::RED, cursor.x, cursor.y, glyphSize.x, glyphSize.y, glyphSlot->bitmap.buffer); - if (glyphSize.y > mMaxGlyphHeight) - mMaxGlyphHeight = glyphSize.y; + if (glyphSize.y > mLegacyMaxGlyphHeight) + mLegacyMaxGlyphHeight = glyphSize.y; return &glyph; } diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index df2b5d506..b3fc40cde 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -21,16 +21,10 @@ class TextCache; -// clang-format off -#define FONT_SIZE_MINI (static_cast(0.030f * \ - std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))) -#define FONT_SIZE_SMALL (static_cast(0.035f * \ - std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))) -#define FONT_SIZE_MEDIUM (static_cast(0.045f * \ - std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))) -#define FONT_SIZE_LARGE (static_cast(0.085f * \ - std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))) -// clang-format on +#define FONT_SIZE_MINI 0.030f * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()) +#define FONT_SIZE_SMALL 0.035f * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()) +#define FONT_SIZE_MEDIUM 0.045f * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()) +#define FONT_SIZE_LARGE 0.085f * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()) #define FONT_PATH_LIGHT ":/fonts/Akrobat-Regular.ttf" #define FONT_PATH_REGULAR ":/fonts/Akrobat-SemiBold.ttf" @@ -42,13 +36,19 @@ class Font : public IReloadable { public: virtual ~Font(); - static std::shared_ptr get(int size, const std::string& path = getDefaultPath()); + static std::shared_ptr get(float size, const std::string& path = getDefaultPath()); // Returns the expected size of a string when rendered. Extra spacing is applied to the Y axis. glm::vec2 sizeText(std::string text, float lineSpacing = 1.5f); - // Returns the size of the overall text area. - const glm::vec2 getTextSize() { return mTextSize; } + // Used to determine mMaxGlyphHeight upfront which is needed for accurate text sizing by + // wrapText and buildTextCache. This is required as the requested font height is not + // guaranteed and can be exceeded by a few pixels for some glyphs. + int loadGlyphs(const std::string& text); + + // This is needed to retain a bug from the legacy theme engine where lineSpacing is not + // sized correctly when using automatic text element sizing. + void useLegacyMaxGlyphHeight() { mMaxGlyphHeight = mLegacyMaxGlyphHeight; } TextCache* buildTextCache(const std::string& text, float offsetX, @@ -86,14 +86,15 @@ public: void reload(ResourceManager& rm) override { rebuildTextures(); } void unload(ResourceManager& rm) override { unloadTextures(); } - int getSize() const { return mFontSize; } + const float getSize() const { return mFontSize; } const std::string& getPath() const { return mPath; } static std::string getDefaultPath() { return FONT_PATH_REGULAR; } static std::shared_ptr getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, const std::shared_ptr& orig, - const float maxHeight = 0.0f); + const float maxHeight = 0.0f, + const bool legacyTheme = false); // Returns an approximation of VRAM used by this font's texture (in bytes). size_t getMemUsage() const; @@ -101,7 +102,7 @@ public: static size_t getTotalMemUsage(); private: - Font(int size, const std::string& path); + Font(float size, const std::string& path); static void initLibrary(); struct FontTexture { @@ -127,7 +128,7 @@ private: const ResourceData data; FT_Face face; - FontFace(ResourceData&& d, int size, const std::string& path); + FontFace(ResourceData&& d, float size, const std::string& path); virtual ~FontFace(); }; @@ -137,6 +138,7 @@ private: glm::vec2 texSize; // In texels. glm::vec2 advance; glm::vec2 bearing; + int rows; }; // Completely recreate the texture data for all textures based on mGlyphs information. @@ -159,7 +161,8 @@ private: void clearFaceCache() { mFaceCache.clear(); } static inline FT_Library sLibrary {nullptr}; - static inline std::map, std::weak_ptr> sFontMap; + static inline std::map, std::weak_ptr> sFontMap; + static inline bool mLegacyTheme {false}; Renderer* mRenderer; std::vector> mTextures; @@ -167,9 +170,10 @@ private: std::map mGlyphMap; const std::string mPath; - glm::vec2 mTextSize; - int mFontSize; + float mFontSize; + float mLetterHeight; int mMaxGlyphHeight; + int mLegacyMaxGlyphHeight; }; // Used to store a sort of "pre-rendered" string. @@ -183,6 +187,7 @@ class TextCache public: struct CacheMetrics { glm::vec2 size; + int maxGlyphHeight; } metrics; void setColor(unsigned int color); From 3de8275db67863272eee347876587380383ca1ae Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 00:43:27 +0200 Subject: [PATCH 44/56] Fixed multiple minor pixel alignment issues. --- es-app/src/views/SystemView.cpp | 4 ++-- es-core/src/components/ComponentList.cpp | 9 +++++---- es-core/src/components/HelpComponent.cpp | 11 +++++++---- es-core/src/components/MenuComponent.cpp | 9 ++++----- es-core/src/components/OptionListComponent.h | 4 ++-- es-core/src/components/SliderComponent.cpp | 11 ++++------- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index e6641645d..2b05a5e3b 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -1183,8 +1183,8 @@ void SystemView::legacyApplyTheme(const std::shared_ptr& theme) mPrimary->applyTheme(theme, "system", "textlist_gamelist", ThemeFlags::ALL); mLegacySystemInfo->setSize(mSize.x, mLegacySystemInfo->getFont()->getLetterHeight() * 2.2f); - mLegacySystemInfo->setPosition(0.0f, std::round(mPrimary->getPosition().y) + - std::round(mPrimary->getSize().y)); + mLegacySystemInfo->setPosition(0.0f, + std::floor(mPrimary->getPosition().y) + mPrimary->getSize().y); mLegacySystemInfo->setBackgroundColor(0xDDDDDDD8); mLegacySystemInfo->setRenderBackground(true); mLegacySystemInfo->setFont( diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index dae7d2a45..b6178ff46 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -290,10 +290,10 @@ void ComponentList::render(const glm::mat4& parentTrans) dim.x = (trans[0].x * dim.x + trans[3].x) - trans[3].x; dim.y = (trans[1].y * dim.y + trans[3].y) - trans[3].y; - const int clipRectPosX {static_cast(std::ceil(trans[3].x))}; - const int clipRectPosY {static_cast(std::ceil(trans[3].y))}; - const int clipRectSizeX {static_cast(std::ceil(dim.x))}; - const int clipRectSizeY {static_cast(std::ceil(dim.y))}; + const int clipRectPosX {static_cast(std::floor(trans[3].x))}; + const int clipRectPosY {static_cast(std::floor(trans[3].y))}; + const int clipRectSizeX {static_cast(std::round(dim.x))}; + const int clipRectSizeY {static_cast(std::ceil(dim.y) + 1.0f)}; mRenderer->pushClipRect(glm::ivec2 {clipRectPosX, clipRectPosY}, glm::ivec2 {clipRectSizeX, clipRectSizeY}); @@ -423,6 +423,7 @@ float ComponentList::getRowHeight(const ComponentListRow& row) const height = row.elements.at(i).component->getSize().y; } + // We round down to avoid separator single-pixel alignment issues. return std::floor(height); } diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index 85736654f..bae9dfc05 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -211,7 +211,11 @@ void HelpComponent::updateGrid() std::vector> labels; float width {0.0f}; - const float height {std::round(font->getLetterHeight() * 1.25f)}; + float height {std::round(font->getLetterHeight() * 1.25f)}; + + // Make sure both text and icons have either odd or equal sizes to avoid alignment issues. + if (static_cast(font->getHeight()) % 2 != static_cast(height) % 2) + --height; // State variable indicating whether the GUI is dimmed. bool isDimmed {mWindow->isBackgroundDimmed()}; @@ -244,8 +248,8 @@ void HelpComponent::updateGrid() mGrid->setSize(width, height); - for (unsigned int i = 0; i < icons.size(); ++i) { - const int col = i * 4; + for (size_t i = 0; i < icons.size(); ++i) { + const size_t col {i * 4}; mGrid->setColWidthPerc(col, icons.at(i)->getSize().x / width); mGrid->setColWidthPerc(col + 1, (mStyle.iconTextSpacing * mRenderer->getScreenWidth()) / width); @@ -256,7 +260,6 @@ void HelpComponent::updateGrid() } mGrid->setPosition({mStyle.position.x, mStyle.position.y, 0.0f}); - mGrid->setOrigin(mStyle.origin); } diff --git a/es-core/src/components/MenuComponent.cpp b/es-core/src/components/MenuComponent.cpp index 12336350a..a0cd27c2e 100644 --- a/es-core/src/components/MenuComponent.cpp +++ b/es-core/src/components/MenuComponent.cpp @@ -11,9 +11,8 @@ #include "Settings.h" #include "components/ButtonComponent.h" -#define BUTTON_GRID_VERT_PADDING std::round(Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.915f) -#define BUTTON_GRID_HORIZ_PADDING \ - std::round(Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.283f) +#define BUTTON_GRID_VERT_PADDING Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.915f +#define BUTTON_GRID_HORIZ_PADDING Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.283f #define TITLE_HEIGHT (mTitle->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) @@ -86,7 +85,7 @@ void MenuComponent::setTitle(std::string title, const std::shared_ptr& fon float MenuComponent::getButtonGridHeight() const { return (mButtonGrid ? mButtonGrid->getSize().y : - Font::get(FONT_SIZE_MEDIUM)->getHeight() + BUTTON_GRID_VERT_PADDING); + Font::get(FONT_SIZE_MEDIUM)->getSize() * 1.5f + BUTTON_GRID_VERT_PADDING); } void MenuComponent::updateSize() @@ -186,7 +185,7 @@ std::shared_ptr makeButtonGrid( std::shared_ptr makeArrow() { auto bracket = std::make_shared(); - bracket->setResize(0, std::round(Font::get(FONT_SIZE_MEDIUM)->getLetterHeight())); + bracket->setResize(0, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()); bracket->setImage(":/graphics/arrow.svg"); return bracket; } diff --git a/es-core/src/components/OptionListComponent.h b/es-core/src/components/OptionListComponent.h index 615e3a35a..8704e68c5 100644 --- a/es-core/src/components/OptionListComponent.h +++ b/es-core/src/components/OptionListComponent.h @@ -86,11 +86,11 @@ public: mText.getFont()->getHeight()); // Position. - mLeftArrow.setPosition(0.0f, std::round((mSize.y - mLeftArrow.getSize().y) / 2.0f)); + mLeftArrow.setPosition(0.0f, (mSize.y - mLeftArrow.getSize().y) / 2.0f); mText.setPosition(mLeftArrow.getPosition().x + mLeftArrow.getSize().x, (mSize.y - mText.getSize().y) / 2.0f); mRightArrow.setPosition(mText.getPosition().x + mText.getSize().x, - std::round((mSize.y - mRightArrow.getSize().y) / 2.0f)); + (mSize.y - mRightArrow.getSize().y) / 2.0f); } bool input(InputConfig* config, Input input) override diff --git a/es-core/src/components/SliderComponent.cpp b/es-core/src/components/SliderComponent.cpp index 452edc09d..7a9a4aea4 100644 --- a/es-core/src/components/SliderComponent.cpp +++ b/es-core/src/components/SliderComponent.cpp @@ -97,11 +97,8 @@ void SliderComponent::render(const glm::mat4& parentTrans) if (mTextCache) mFont->renderTextCache(mTextCache.get()); - const float barPosY {mBarHeight == 1.0f ? std::floor(mSize.y / 2.0f - mBarHeight / 2.0f) : - std::round(mSize.y / 2.0f - mBarHeight / 2.0f)}; - // Render bar. - mRenderer->drawRect(mKnob.getSize().x / 2.0f, barPosY, width, mBarHeight, 0x777777FF, + mRenderer->drawRect(mKnob.getSize().x / 2.0f, mSize.y / 2.0f, width, mBarHeight, 0x777777FF, 0x777777FF); // Render knob. @@ -154,7 +151,7 @@ void SliderComponent::onValueChanged() mTextCache->metrics.size.x = textSize.x; // Fudge the width. } - mKnob.setResize(0.0f, std::round(mSize.y * 0.7f)); + mKnob.setResize(0.0f, mSize.y * 0.7f); float barLength { mSize.x - mKnob.getSize().x - @@ -175,11 +172,11 @@ void SliderComponent::onValueChanged() barHeight = 1; // Resize the knob one pixel if necessary to keep the bar centered. - if (barHeight % 2 == 0 && static_cast(mKnob.getSize().y) % 2 != 0) { + if (barHeight % 2 == 0 && static_cast(std::round(mKnob.getSize().y)) % 2 != 0) { mKnob.setResize(mKnob.getSize().x - 1.0f, mKnob.getSize().y - 1.0f); setSize(getSize().x, getSize().y - 1.0f); } - else if (barHeight == 1 && static_cast(mKnob.getSize().y) % 2 == 0) { + else if (barHeight == 1 && static_cast(std::round(mKnob.getSize().y)) % 2 == 0) { mKnob.setResize(mKnob.getSize().x - 1.0f, mKnob.getSize().y - 1); setSize(getSize().x, getSize().y - 1.0f); } From ddf5684a9e6a5595a1cefefe35674278ad636812 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 00:45:07 +0200 Subject: [PATCH 45/56] (slate-DE) Updated to adjust to the more accurate font rendering that was just introduced. --- themes/slate-DE/theme.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/themes/slate-DE/theme.xml b/themes/slate-DE/theme.xml index 7493961cd..4ae29cb0d 100644 --- a/themes/slate-DE/theme.xml +++ b/themes/slate-DE/theme.xml @@ -206,13 +206,13 @@ md_lbl_genre, md_lbl_players, md_lbl_lastplayed "> 0.14 0.02 ./core/fonts/Exo2-BoldCondensed.otf - 0.02 + 0.0195 uppercase 0.14 0.02 ./core/fonts/Exo2-RegularCondensed.otf - 0.02 + 0.0195 uppercase @@ -225,14 +225,14 @@ 7 description ./core/fonts/Exo2-SemiBoldCondensed.otf - 0.02 + 0.0195 uppercase 1.2 0.14 0.02 ./core/fonts/Exo2-RegularCondensed.otf - 0.02 + 0.0195 uppercase From 97a9347d6795c7f20c14cea81bae1cb445c1749e Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 01:06:02 +0200 Subject: [PATCH 46/56] Improved font sizing for the launch screen, scraper and help system. --- es-app/src/guis/GuiLaunchScreen.cpp | 17 ++++++++--------- es-app/src/guis/GuiScraperSearch.cpp | 2 +- es-core/src/components/HelpComponent.cpp | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/es-app/src/guis/GuiLaunchScreen.cpp b/es-app/src/guis/GuiLaunchScreen.cpp index af5955a56..76ea4a1c2 100644 --- a/es-app/src/guis/GuiLaunchScreen.cpp +++ b/es-app/src/guis/GuiLaunchScreen.cpp @@ -55,8 +55,8 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game) // Title. mTitle = std::make_shared( "LAUNCHING GAME", - Font::get(static_cast( - titleFontSize * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))), + Font::get(titleFontSize * + std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth())), 0x666666FF, ALIGN_CENTER); mGrid->setEntry(mTitle, glm::ivec2 {1, 1}, false, true, glm::ivec2 {1, 1}); @@ -75,8 +75,8 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game) // Game name. mGameName = std::make_shared( "GAME NAME", - Font::get(static_cast( - gameNameFontSize * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))), + Font::get(gameNameFontSize * + std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth())), 0x444444FF, ALIGN_CENTER); mGrid->setEntry(mGameName, glm::ivec2 {1, 5}, false, true, glm::ivec2 {1, 1}); @@ -108,11 +108,10 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game) float maxWidth {Renderer::getScreenWidth() * maxWidthModifier}; float minWidth {Renderer::getScreenWidth() * minWidthModifier}; - float fontWidth { - Font::get(static_cast(gameNameFontSize * std::min(Renderer::getScreenHeight(), - Renderer::getScreenWidth()))) - ->sizeText(Utils::String::toUpper(game->getName())) - .x}; + float fontWidth {Font::get(gameNameFontSize * + std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth())) + ->sizeText(Utils::String::toUpper(game->getName())) + .x}; // Add a bit of width to compensate for the left and right spacers. fontWidth += Renderer::getScreenWidth() * 0.05f; diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index f426ec1f6..202b16af1 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -240,7 +240,7 @@ void GuiScraperSearch::resizeMetadata() { mMD_Grid->setSize(mGrid.getColWidth(2), mGrid.getRowHeight(1)); if (mMD_Grid->getSize().y > mMD_Pairs.size()) { - const int fontHeight {static_cast(mMD_Grid->getSize().y / mMD_Pairs.size() * 0.8f)}; + const float fontHeight {mMD_Grid->getSize().y / mMD_Pairs.size() * 0.8f}; auto fontLbl = Font::get(fontHeight, FONT_PATH_REGULAR); auto fontComp = Font::get(fontHeight, FONT_PATH_LIGHT); diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index bae9dfc05..4dcd468d6 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -248,8 +248,8 @@ void HelpComponent::updateGrid() mGrid->setSize(width, height); - for (size_t i = 0; i < icons.size(); ++i) { - const size_t col {i * 4}; + for (int i = 0; i < static_cast(icons.size()); ++i) { + const int col {i * 4}; mGrid->setColWidthPerc(col, icons.at(i)->getSize().x / width); mGrid->setColWidthPerc(col + 1, (mStyle.iconTextSpacing * mRenderer->getScreenWidth()) / width); From 4513b203398d5d0c27bc844699a635c1c329ddbd Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 01:20:31 +0200 Subject: [PATCH 47/56] Some minor font sizing improvements. Also fixed a few implicit casts. --- es-app/src/views/SystemView.cpp | 3 +-- es-core/src/components/SliderComponent.cpp | 2 +- es-core/src/resources/Font.cpp | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 2b05a5e3b..11a5131ec 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -1187,8 +1187,7 @@ void SystemView::legacyApplyTheme(const std::shared_ptr& theme) std::floor(mPrimary->getPosition().y) + mPrimary->getSize().y); mLegacySystemInfo->setBackgroundColor(0xDDDDDDD8); mLegacySystemInfo->setRenderBackground(true); - mLegacySystemInfo->setFont( - Font::get(static_cast(0.035f * mSize.y), Font::getDefaultPath())); + mLegacySystemInfo->setFont(Font::get(0.035f * mSize.y, Font::getDefaultPath())); mLegacySystemInfo->setColor(0x000000FF); mLegacySystemInfo->setUppercase(true); mLegacySystemInfo->setZIndex(50.0f); diff --git a/es-core/src/components/SliderComponent.cpp b/es-core/src/components/SliderComponent.cpp index 7a9a4aea4..4402d9277 100644 --- a/es-core/src/components/SliderComponent.cpp +++ b/es-core/src/components/SliderComponent.cpp @@ -121,7 +121,7 @@ void SliderComponent::setValue(float value) void SliderComponent::onSizeChanged() { if (!mSuffix.empty()) - mFont = Font::get(static_cast(mSize.y), FONT_PATH_LIGHT); + mFont = Font::get(mSize.y, FONT_PATH_LIGHT); onValueChanged(); } diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index b44d75a96..c6bf3ed02 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -413,7 +413,7 @@ glm::vec2 Font::getWrappedTextCursorOffset(const std::string& wrappedText, float Font::getLetterHeight() { if (mLetterHeight == 0.0f) - return mFontSize * 0.737; // Only needed if face does not contain the letter 'S'. + return mFontSize * 0.737f; // Only needed if face does not contain the letter 'S'. else return mLetterHeight; } @@ -593,7 +593,8 @@ Font::FontFace::FontFace(ResourceData&& d, float size, const std::string& path) // Even though a fractional font size can be requested, the glyphs will always be rounded // to integers. It's not useless to call FT_Set_Char_Size() instead of FT_Set_Pixel_Sizes() // though as the glyphs will still be much more evenely sized across different resolutions. - FT_Set_Char_Size(face, 0.0f, size * 64.0f, 0, 0); + FT_Set_Char_Size(face, static_cast(0.0f), static_cast(size * 64.0f), 0, + 0); } Font::FontFace::~FontFace() @@ -659,7 +660,7 @@ void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize, return; // Yes. } - mTextures.emplace_back(std::make_unique(mFontSize)); + mTextures.emplace_back(std::make_unique(static_cast(std::round(mFontSize)))); tex_out = mTextures.back().get(); tex_out->initTexture(); From a8d1c4a2e122a68783c9f74c99a77398aeb8d6bd Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 20:33:12 +0200 Subject: [PATCH 48/56] Fixed an issue in CarouselComponent where setting itemScale to less than 1.0 did not work as expected. Also fixed a problem where carousel text entries did not get multiplied by itemScale. --- .../components/primary/CarouselComponent.h | 32 +++++++++++++------ es-core/src/resources/Font.cpp | 11 ++++--- es-core/src/resources/Font.h | 3 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/es-core/src/components/primary/CarouselComponent.h b/es-core/src/components/primary/CarouselComponent.h index 6f3351918..e8cc42093 100644 --- a/es-core/src/components/primary/CarouselComponent.h +++ b/es-core/src/components/primary/CarouselComponent.h @@ -227,7 +227,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr(false, dynamic); item->setLinearInterpolation(mLinearInterpolation); item->setMipmapping(true); - item->setMaxSize(glm::round(mItemSize * mItemScale)); + item->setMaxSize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); item->applyTheme(theme, "system", "image_logo", ThemeFlags::PATH | ThemeFlags::COLOR); item->setRotateByTargetSize(true); @@ -241,7 +241,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr(false, dynamic); item->setLinearInterpolation(mLinearInterpolation); item->setMipmapping(true); - item->setMaxSize(glm::round(mItemSize * mItemScale)); + item->setMaxSize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); item->setImage(entry.data.itemPath); item->applyTheme(theme, "system", "", ThemeFlags::ALL); item->setRotateByTargetSize(true); @@ -252,7 +252,8 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr(false, dynamic); defaultItem->setLinearInterpolation(mLinearInterpolation); defaultItem->setMipmapping(true); - defaultItem->setMaxSize(glm::round(mItemSize * mItemScale)); + defaultItem->setMaxSize( + glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); defaultItem->setImage(entry.data.defaultItemPath); defaultItem->applyTheme(theme, "system", "", ThemeFlags::ALL); defaultItem->setRotateByTargetSize(true); @@ -276,7 +277,8 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptr( nameEntry, mFont, 0x000000FF, mItemHorizontalAlignment, mItemVerticalAlignment, - glm::vec3 {0.0f, 0.0f, 0.0f}, glm::round(mItemSize * mItemScale), 0x00000000); + glm::vec3 {0.0f, 0.0f, 0.0f}, + glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f)), 0x00000000); if (legacyMode) { text->applyTheme(theme, "system", "text_logoText", ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | @@ -324,7 +326,7 @@ void CarouselComponent::updateEntry(Entry& entry, const std::shared_ptr(false, true); item->setLinearInterpolation(mLinearInterpolation); item->setMipmapping(true); - item->setMaxSize(glm::round(mItemSize * mItemScale)); + item->setMaxSize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); item->setImage(entry.data.itemPath); item->applyTheme(theme, "system", "", ThemeFlags::ALL); item->setRotateByTargetSize(true); @@ -769,9 +771,17 @@ template void CarouselComponent::render(const glm::mat4& parentT if (singleEntry) distance = 0.0f; - float scale {1.0f + ((mItemScale - 1.0f) * (1.0f - fabsf(distance)))}; - scale = std::min(mItemScale, std::max(1.0f, scale)); - scale /= mItemScale; + float scale {0.0f}; + + if (mItemScale >= 1.0f) { + scale = 1.0f + ((mItemScale - 1.0f) * (1.0f - fabsf(distance))); + scale = std::min(mItemScale, std::max(1.0f, scale)); + scale /= mItemScale; + } + else { + scale = 1.0f + ((1.0f - mItemScale) * (fabsf(distance) - 1.0f)); + scale = std::max(mItemScale, std::min(1.0f, scale)); + } glm::mat4 itemTrans {carouselTrans}; if (singleEntry) @@ -1012,7 +1022,7 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, } if (elem->has("itemScale")) - mItemScale = glm::clamp(elem->get("itemScale"), 0.5f, 3.0f); + mItemScale = glm::clamp(elem->get("itemScale"), 0.2f, 3.0f); if (elem->has("itemTransitions")) { const std::string itemTransitions {elem->get("itemTransitions")}; @@ -1206,7 +1216,9 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, } } - mFont = Font::getFromTheme(elem, properties, mFont, 0.0f, mLegacyMode); + // For non-legacy themes, scale the font size with the itemScale property value. + mFont = Font::getFromTheme(elem, properties, mFont, 0.0f, mLegacyMode, + (mLegacyMode ? 1.0f : (mItemScale >= 1.0f ? mItemScale : 1.0f))); if (elem->has("textColor")) mTextColor = elem->get("textColor"); diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index c6bf3ed02..6a3924989 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -422,7 +422,8 @@ std::shared_ptr Font::getFromTheme(const ThemeData::ThemeElement* elem, unsigned int properties, const std::shared_ptr& orig, const float maxHeight, - const bool legacyTheme) + const bool legacyTheme, + const float sizeMultiplier) { mLegacyTheme = legacyTheme; @@ -438,6 +439,8 @@ std::shared_ptr Font::getFromTheme(const ThemeData::ThemeElement* elem, if (properties & FONT_SIZE && elem->has("fontSize")) { size = glm::clamp(screenHeight * elem->get("fontSize"), screenHeight * 0.001f, screenHeight * 1.5f); + // This is used by the carousel where the itemScale property also scales the font size. + size *= sizeMultiplier; } if (maxHeight != 0.0f && size > maxHeight) @@ -741,10 +744,8 @@ Font::Glyph* Font::getGlyph(const unsigned int id) } // Use the letter 'S' as a size reference. - if (mLetterHeight == 0 && id == 'S') { - const float ratio {static_cast(glyphSize.y) / std::round(mFontSize)}; - mLetterHeight = mFontSize * ratio; - } + if (mLetterHeight == 0 && id == 'S') + mLetterHeight = static_cast(glyphSize.y); // Create glyph. Glyph& glyph {mGlyphMap[id]}; diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index b3fc40cde..7ba8b93d5 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -94,7 +94,8 @@ public: unsigned int properties, const std::shared_ptr& orig, const float maxHeight = 0.0f, - const bool legacyTheme = false); + const bool legacyTheme = false, + const float sizeMultiplier = 1.0f); // Returns an approximation of VRAM used by this font's texture (in bytes). size_t getMemUsage() const; From 18bbd97433c5ba8bc650605a7c4bb9f288833119 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 20:34:58 +0200 Subject: [PATCH 49/56] Fixed an issue where the textlist selector would not get sized correctly. --- es-core/src/components/primary/TextListComponent.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/es-core/src/components/primary/TextListComponent.h b/es-core/src/components/primary/TextListComponent.h index 6cfebe848..f1013897d 100644 --- a/es-core/src/components/primary/TextListComponent.h +++ b/es-core/src/components/primary/TextListComponent.h @@ -489,11 +489,10 @@ template void TextListComponent::render(const glm::mat4& parentT // Currently selected item text might be looping. if (mCursor == i && mLoopOffset1 > 0) { drawTrans = glm::translate( - drawTrans, - glm::round(offset - glm::vec3 {static_cast(mLoopOffset1), 0.0f, 0.0f})); + drawTrans, offset - glm::vec3 {static_cast(mLoopOffset1), 0.0f, 0.0f}); } else { - drawTrans = glm::translate(drawTrans, glm::round(offset)); + drawTrans = glm::translate(drawTrans, offset); } // Needed to avoid flickering when returning to the start position. @@ -508,8 +507,7 @@ template void TextListComponent::render(const glm::mat4& parentT mLoopScroll = true; drawTrans = trans; drawTrans = glm::translate( - drawTrans, - glm::round(offset - glm::vec3 {static_cast(mLoopOffset2), 0.0f, 0.0f})); + drawTrans, offset - glm::vec3 {static_cast(mLoopOffset2), 0.0f, 0.0f}); mRenderer->setMatrix(drawTrans); font->renderTextCache(entry.data.textCache.get()); } @@ -567,7 +565,7 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, } setFont(Font::getFromTheme(elem, properties, mFont, 0.0f, mLegacyMode)); - const float selectorHeight {mFont->getSize() * mLineSpacing}; + const float selectorHeight {mFont->getHeight(mLineSpacing)}; setSelectorHeight(selectorHeight); if (properties & ALIGNMENT) { From c50b6b35f48905315569037bbae7a2fb4314dabd Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 20:39:12 +0200 Subject: [PATCH 50/56] Removed some unnecessary roundings. --- es-core/src/components/ComponentList.cpp | 21 +++++++++---------- es-core/src/components/MenuComponent.cpp | 4 ++-- es-core/src/components/NinePatchComponent.cpp | 5 ++--- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index b6178ff46..9be125af2 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -299,7 +299,7 @@ void ComponentList::render(const glm::mat4& parentTrans) glm::ivec2 {clipRectSizeX, clipRectSizeY}); // Scroll the camera. - trans = glm::translate(trans, glm::vec3 {0.0f, std::round(-mCameraOffset), 0.0f}); + trans = glm::translate(trans, glm::vec3 {0.0f, -mCameraOffset, 0.0f}); glm::mat4 loopTrans {trans}; @@ -382,14 +382,14 @@ void ComponentList::render(const glm::mat4& parentTrans) const float selectedRowHeight {getRowHeight(mEntries.at(mCursor).data)}; if (mOpacity == 1.0f) { - mRenderer->drawRect(0.0f, mSelectorBarOffset, std::round(mSize.x), selectedRowHeight, - 0xFFFFFFFF, 0xFFFFFFFF, false, mOpacity, mDimming, + mRenderer->drawRect(0.0f, mSelectorBarOffset, mSize.x, selectedRowHeight, 0xFFFFFFFF, + 0xFFFFFFFF, false, mOpacity, mDimming, Renderer::BlendFactor::ONE_MINUS_DST_COLOR, Renderer::BlendFactor::ZERO); - mRenderer->drawRect(0.0f, mSelectorBarOffset, std::round(mSize.x), selectedRowHeight, - 0x777777FF, 0x777777FF, false, mOpacity, mDimming, - Renderer::BlendFactor::ONE, Renderer::BlendFactor::ONE); + mRenderer->drawRect(0.0f, mSelectorBarOffset, mSize.x, selectedRowHeight, 0x777777FF, + 0x777777FF, false, mOpacity, mDimming, Renderer::BlendFactor::ONE, + Renderer::BlendFactor::ONE); } for (auto it = drawAfterCursor.cbegin(); it != drawAfterCursor.cend(); ++it) @@ -403,14 +403,13 @@ void ComponentList::render(const glm::mat4& parentTrans) // Draw separators. float y {0.0f}; for (unsigned int i = 0; i < mEntries.size(); ++i) { - mRenderer->drawRect(0.0f, y, std::round(mSize.x), - 1.0f * Renderer::getScreenHeightModifier(), 0xC6C7C6FF, 0xC6C7C6FF, - false, mOpacity, mDimming); + mRenderer->drawRect(0.0f, y, mSize.x, 1.0f * Renderer::getScreenHeightModifier(), + 0xC6C7C6FF, 0xC6C7C6FF, false, mOpacity, mDimming); y += getRowHeight(mEntries.at(i).data); } - mRenderer->drawRect(0.0f, y, std::round(mSize.x), 1.0f * Renderer::getScreenHeightModifier(), - 0xC6C7C6FF, 0xC6C7C6FF, false, mOpacity, mDimming); + mRenderer->drawRect(0.0f, y, mSize.x, 1.0f * Renderer::getScreenHeightModifier(), 0xC6C7C6FF, + 0xC6C7C6FF, false, mOpacity, mDimming); mRenderer->popClipRect(); } diff --git a/es-core/src/components/MenuComponent.cpp b/es-core/src/components/MenuComponent.cpp index a0cd27c2e..0fe271e19 100644 --- a/es-core/src/components/MenuComponent.cpp +++ b/es-core/src/components/MenuComponent.cpp @@ -113,7 +113,7 @@ void MenuComponent::updateSize() void MenuComponent::onSizeChanged() { - mBackground.fitTo(mSize, glm::vec3 {}, glm::vec2 {-32.0f, -32.0f}); + mBackground.fitTo(mSize, glm::vec3 {0.0f, 0.0f, 0.0f}, glm::vec2 {-32.0f, -32.0f}); // Update grid row/column sizes. mGrid.setRowHeightPerc(0, TITLE_HEIGHT / mSize.y / 2.0f); @@ -131,7 +131,7 @@ void MenuComponent::onSizeChanged() mTitle->setSize(titleSize.x - indicatorsSize, titleSize.y); glm::vec3 titlePos {mTitle->getPosition()}; - mTitle->setPosition(titlePos.x + std::round(indicatorsSize / 2.0f), titlePos.y, titlePos.z); + mTitle->setPosition(titlePos.x + indicatorsSize / 2.0f, titlePos.y, titlePos.z); } void MenuComponent::addButton(const std::string& name, diff --git a/es-core/src/components/NinePatchComponent.cpp b/es-core/src/components/NinePatchComponent.cpp index 39e438ac6..f70251f25 100644 --- a/es-core/src/components/NinePatchComponent.cpp +++ b/es-core/src/components/NinePatchComponent.cpp @@ -60,9 +60,8 @@ void NinePatchComponent::buildVertices() else { // Scale the corner size relative to the screen resolution (using the medium sized // default font as size reference). - relCornerSize = - glm::round(mCornerSize * (Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * - (mSharpCorners == true ? 0.0568f : 0.09f) / 2.0f)); + relCornerSize = mCornerSize * (Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * + (mSharpCorners == true ? 0.0568f : 0.09f) / 2.0f); } glm::vec2 texSize {relCornerSize * 3.0f}; From 3e8c7850c7d861c97f5a484a1127752fa4e9a525 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 20:40:28 +0200 Subject: [PATCH 51/56] (slate-DE) Updated some font sizes to align with the changes to the carousel text sizing. --- themes/slate-DE/theme_engine_test_1.xml | 2 +- themes/slate-DE/theme_engine_test_2.xml | 2 +- themes/slate-DE/theme_engine_test_3.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/themes/slate-DE/theme_engine_test_1.xml b/themes/slate-DE/theme_engine_test_1.xml index af459be8e..1fa9edb3c 100644 --- a/themes/slate-DE/theme_engine_test_1.xml +++ b/themes/slate-DE/theme_engine_test_1.xml @@ -22,7 +22,7 @@ -0.05 ${system.fullName} ./core/fonts/Exo2-RegularCondensed.otf - 0.070 + 0.056 uppercase 1.2 diff --git a/themes/slate-DE/theme_engine_test_2.xml b/themes/slate-DE/theme_engine_test_2.xml index f56fde3d5..7d5336c17 100644 --- a/themes/slate-DE/theme_engine_test_2.xml +++ b/themes/slate-DE/theme_engine_test_2.xml @@ -107,7 +107,7 @@ 0 0 ./core/fonts/Exo2-RegularCondensed.otf - 0.070 + 0.056 uppercase 1.2 80 diff --git a/themes/slate-DE/theme_engine_test_3.xml b/themes/slate-DE/theme_engine_test_3.xml index cd3b46966..f3e8e7d22 100644 --- a/themes/slate-DE/theme_engine_test_3.xml +++ b/themes/slate-DE/theme_engine_test_3.xml @@ -111,7 +111,7 @@ 1 323232CC ./core/fonts/Exo2-RegularCondensed.otf - 0.050 + 0.032 uppercase 1.2 60 From 7c1a48ec5c1a7dbc60e2dddee5bc0f94194a39ed Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 20:49:22 +0200 Subject: [PATCH 52/56] Removed two unnecessary roundings from OptionListComponent. --- es-core/src/components/OptionListComponent.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/OptionListComponent.h b/es-core/src/components/OptionListComponent.h index 8704e68c5..40ca877c8 100644 --- a/es-core/src/components/OptionListComponent.h +++ b/es-core/src/components/OptionListComponent.h @@ -327,7 +327,7 @@ private: mText.setText(ss.str()); mText.setSize(0, mText.getSize().y); setSize(mText.getSize().x + mRightArrow.getSize().x + - std::round(Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.68f), + Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.68f, mText.getSize().y); if (mParent) // Hack since there's no "on child size changed" callback. mParent->onSizeChanged(); @@ -351,7 +351,7 @@ private: mText.setSize(0.0f, mText.getSize().y); setSize(mText.getSize().x + mLeftArrow.getSize().x + mRightArrow.getSize().x + - std::round(Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.68f), + Font::get(FONT_SIZE_MEDIUM)->getLetterHeight() * 0.68f, mText.getSize().y); if (mParent) // Hack since there's no "on child size changed" callback. mParent->onSizeChanged(); From 3252f8fa1ba279db529ef66d5f45c551b43dd982 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 21:11:16 +0200 Subject: [PATCH 53/56] Reintroduced a transformation rounding in GuiComponent as some GPU drivers went crazy without it. --- es-core/src/GuiComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index 93085c563..d66849627 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -206,7 +206,7 @@ void GuiComponent::setDimming(float dimming) const glm::mat4& GuiComponent::getTransform() { mTransform = Renderer::getIdentity(); - mTransform = glm::translate(mTransform, mPosition); + mTransform = glm::translate(mTransform, glm::round(mPosition)); if (mScale != 1.0f) mTransform = glm::scale(mTransform, glm::vec3 {mScale}); From 132c18eae10769a9e9116110b8d9a13e2dfbbea7 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 22:00:06 +0200 Subject: [PATCH 54/56] Documentation update. --- CHANGELOG.md | 2 ++ THEMES-DEV.md | 40 ++++++++++++++++++++++++---------------- USERGUIDE-DEV.md | 2 +- USERGUIDE.md | 2 +- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a15f4ee1..fd496c2dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -213,6 +213,8 @@ * When a legacy theme set had a video view style but did not have a valid md_video entry then the video player would still start (and play the audio) * Clearing a game in the metadata editor would sometimes not remove all media files (if there were both a .jpg and a .png for a certain file type) * The tile property for the image element did not work correctly with SVG images +* Defining an itemScale (logoScale) property lower than 1.0 for the carousel did not work correctly +* Carousel text did not get scaled/multiplied correctly with the itemScale property (bug retained for legacy themes for maximum backward compatibility) * Letters would sometimes get rendered with ugly edge artifacts, visible when scaling text on the carousel * Text opacity did not work correctly in some places, such as for the help prompts * ScrollableContainer faded semi-transparent text to fully opaque when resetting diff --git a/THEMES-DEV.md b/THEMES-DEV.md index 107a0fe60..d54809909 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -70,7 +70,7 @@ With the new theme engine the view presets were removed and the only views now a In addition to the variant support which provides an unlimited flexibility for creating custom theme profiles, support for specific aspect ratios was introduced. This makes it possible to define different theme configuration for different display aspect ratios and to provide the user with the option to choose between these from the _UI Settings_ menu. That could for example be choice between a 16:9 and a 4:3 layout, or perhaps also a vertical screen orientation layout in addition to these. -As well new theming abilities like Lottie animations were added with the new theme engine. +As well new theming abilities like GIF and Lottie animations were added with the new theme engine. The following are the most important changes compared to the legacy theme structure: @@ -95,6 +95,13 @@ Attempting to use any of the legacy logic in the new theme structure will make t Except the points mentioned above, theme configuration looks pretty similar to the legacy theme structure, so anyone having experience with these older themes should hopefully feel quite at home with the new theme engine. Probably the most important thing to keep in mind is that as there are no longer any view presets available, some more effort is needed from the theme developer to define values for some elements. This is especially true for zIndex values as elements could now be hidden by other elements if care is not taken to explicitly set the zIndex for each of them. This additional work is however a small price to pay for the much more powerful and flexible theming functionality provided by the new theme engine. +Note that the legacy theme engine had quite inaccurate text sizing and font rendering and while this has been greatly improved in the new engine, for legacy themes most old bugs are retained for maximum backward compatibility. This means that you may need to revise font sizes and text placements when porting a legacy theme to the new engine. Here are some examples: + +* Line spacing for the textlist element was not consistently applied across different screen resolutions +* Carousel text entries did not multiply the font size by the itemScale (logoScale) property value +* The defined line spacing was not always applied for automatically sized text elements +* Font sizes were rounded to integers, leading to imprecise text sizing across different resolutions (the rounding was also done incorrectly) + ## Simple example Here is a very simple theme that changes the color of the game name text: @@ -1240,8 +1247,8 @@ Properties: * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. - - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value can get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the reference 'S' character so other glyphs may not fill this area, or they may exceed this area. + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value may get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. - Default is `0.045` * `horizontalAlignment` - type: STRING - Controls alignment on the X axis. @@ -1257,7 +1264,7 @@ Properties: - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - - Controls the space between lines (as a multiple of font height). + - Controls the space between lines (as a multiple of the font height). Due to the imprecise nature of typefaces where certain glyphs (characters) may exceed the requested font size, it's recommended to keep this value at around `1.1` or higher for multi-line text fields. This way overlapping glyphs or characters being cut off at the top or bottom will be prevented. - Minimum value is `0.5` and maximum value is `3` - Default is `1.5` * `opacity` - type: FLOAT @@ -1310,8 +1317,8 @@ Properties: * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. - - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value can get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the reference 'S' character so other glyphs may not fill this area, or they may exceed this area. + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value may get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. - Default is `0.045` * `horizontalAlignment` - type: STRING - Controls alignment on the X axis. @@ -1383,8 +1390,8 @@ Properties: * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. - - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value can get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the reference 'S' character so other glyphs may not fill this area, or they may exceed this area. + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value may get clamped to a larger relative size. The font is allowed to overflow the height of the element by 100%, i.e. `fontSize` can be set to twice that of the y axis of the `size` property. Any value above that will be clamped. - Default is `0.045` * `color` - type: COLOR * `backgroundColor` - type: COLOR @@ -1519,12 +1526,12 @@ Properties: - Minimum value is `0` and maximum value is `20` - Default is `8` * `itemSize` - type: NORMALIZED_PAIR - - Both axes need to be defined. + - Size of the item prior to multiplication by the `itemScale` value, i.e. the size of all unselected items. Both axes need to be defined. - Minimum value per axis is `0.05` and maximum value per axis is `1` - Default is `0.25 0.155` * `itemScale` - type: FLOAT. - Selected item is increased in size by this scale. - - Minimum value is `0.5` and maximum value is `3` + - Minimum value is `0.2` and maximum value is `3` - Default is `1.2` * `itemTransitions` - type: STRING - How to render item transitions when navigating the carousel. By default a slide animation will be played when moving between items but if this property is set to `instant` instead then the transitions will be immediate. @@ -1599,13 +1606,14 @@ Properties: * `fontPath` - type: PATH - Path to a TrueType font (.ttf) used as fallback if there is no `staticItem` / `itemType` image defined or found, and if `defaultItem` has not been defined. * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the reference 'S' character so other glyphs may not fill this area, or they may exceed this area. This property value is effectively multiplied by the `itemScale` value for the currently selected item (but if this property is omitted then the default value will not get multiplied by `itemScale`). + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value may get clamped to a larger relative size. - Default is `0.085` * `letterCase` - type: STRING - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - - Controls the space between lines (as a multiple of font height). + - Controls the space between lines (as a multiple of the font height). Due to the imprecise nature of typefaces where certain glyphs (characters) may exceed the requested font size, it's recommended to keep this value at around `1.1` or higher. This way overlapping glyphs or characters being cut off at the top or bottom will be prevented. - Minimum value is `0.5` and maximum value is `3` - Default is `1.5` * `fadeAbovePrimary` - type: BOOLEAN @@ -1662,7 +1670,7 @@ Properties: - Secondary color; what this means depends on the text list. For example, for game lists, it is the color of a folder. * `fontPath` - type: PATH * `fontSize` - type: FLOAT - - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). This calculation is based on the reference 'S' character so other glyphs may not fill this area, or they may exceed this area. - Default is `0.045` * `horizontalAlignment` - type: STRING - Controls alignment on the X axis. @@ -1675,7 +1683,7 @@ Properties: - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - - Controls the space between lines (as a multiple of font height). + - Controls the space between lines (as a multiple of the font height). - Minimum value is `0.5` and maximum value is `3` - Default is `1.5` * `indicators` - type: STRING @@ -1745,8 +1753,8 @@ Properties: - Default is the same value as iconColor. * `fontPath` - type: PATH * `fontSize` - type: FLOAT - - This property implicitly sets the icon size and is therefore the means to change the overall size of the helpsystem element. This calculation is based on the font's bounding box, the actual glyphs (characters) don't normally fill this entire area. - - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value can get clamped to a larger relative size. + - This property implicitly sets the icon size and is therefore the means to change the overall size of the helpsystem element. This calculation is based on the reference 'S' character so other glyphs may not fill this area, or they may exceed this area. + - Minimum value is `0.001` and maximum value is `1.5`. Note that when running at a really low resolution, the minimum value may get clamped to a larger relative size. - Default is `0.035` * `entrySpacing` - type: FLOAT - Spacing between the help element pairs. diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index e38ac74bb..8b38acf2c 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -2923,7 +2923,7 @@ For this example, we've downloaded the [alekfull-nx-es-de](https://github.com/an ~/.emulationstation/themes/es-theme-carbon ``` -We now have four entries in the _Theme set_ selector in the UI settings menu, i.e. _alekfull-nx-es-de, es-theme-carbon, modern-DE and slate-DE_. +We now have four entries in the _Theme set_ selector in the UI settings menu, i.e. _alekfull-nx-es-de, es-theme-carbon, modern-DE_ and _slate-DE_. Although you should place additional themes in your ES-DE home directory, the default slate-DE and modern-DE themes are located in the installation folder as they come bundled with the application. For example this could be `/usr/share/emulationstation/themes/` or `/usr/local/share/emulationstation/themes/` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes/` on macOS or `C:\Program Files\EmulationStation-DE\themes\` on Windows. If using the portable ES-DE release on Windows, the themes folder will be located in the root of the EmulationStation-DE directory. diff --git a/USERGUIDE.md b/USERGUIDE.md index e224b3ab2..580117250 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -2833,7 +2833,7 @@ For this example, we've downloaded the [alekfull-nx-retropie](https://github.com ~/.emulationstation/themes/es-theme-carbon ``` -We now have four entries in the _Theme set_ selector in the UI settings menu, i.e. _alekfull-nx-retropie, es-theme-carbon, modern-DE and rbsimple-DE_. +We now have four entries in the _Theme set_ selector in the UI settings menu, i.e. _alekfull-nx-retropie, es-theme-carbon, modern-DE_ and _rbsimple-DE_. Although you should place additional themes in your ES-DE home directory, the default rbsimple-DE and modern-DE themes are located in the installation folder as they come bundled with the application. For example this could be `/usr/share/emulationstation/themes/` or `/usr/local/share/emulationstation/themes/` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes/` on macOS or `C:\Program Files\EmulationStation-DE\themes\` on Windows. If using the portable ES-DE release on Windows, the themes folder will be located in the root of the EmulationStation-DE directory. From cbea006a7068d35fe117629d315190fdd3cb48bd Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 25 Oct 2022 22:58:03 +0200 Subject: [PATCH 55/56] Removed support for setting logoScale to lower than 1.0 for legacy themes as it introduced backward compatibility issues. --- es-core/src/components/primary/CarouselComponent.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/es-core/src/components/primary/CarouselComponent.h b/es-core/src/components/primary/CarouselComponent.h index e8cc42093..1311782c5 100644 --- a/es-core/src/components/primary/CarouselComponent.h +++ b/es-core/src/components/primary/CarouselComponent.h @@ -1155,8 +1155,9 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, // Legacy themes. if (mLegacyMode) { + // Don't allow logoScale below 1.0 for legacy themes as it introduces compatibility issues. if (elem->has("logoScale")) - mItemScale = glm::clamp(elem->get("logoScale"), 0.5f, 3.0f); + mItemScale = glm::clamp(elem->get("logoScale"), 1.0f, 3.0f); if (elem->has("logoSize")) { // Keep size within a 0.05 and 1.0 multiple of the screen size. glm::vec2 itemSize {elem->get("logoSize")}; From 2fd5580e1365ad27a1dc489e49c85681e8014173 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 26 Oct 2022 18:42:42 +0200 Subject: [PATCH 56/56] Fixed a small font offset problem mostly seen at really low resolutions. --- es-core/src/resources/Font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 6a3924989..51d21e8e5 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -154,7 +154,7 @@ TextCache* Font::buildTextCache(const std::string& text, yBot = getHeight(lineSpacing); } - float y {offset[1] + (yBot + yTop) / 2.0f}; + float y {std::round(offset[1] + (yBot + yTop) / 2.0f)}; // Vertices by texture. std::map> vertMap;