From 7193d0984547404c9cb5fc14e0b251b34d9beb29 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sat, 4 Sep 2021 21:05:20 +0200 Subject: [PATCH 001/128] theme definition for badges in rbsimple --- themes/rbsimple-DE/theme.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 2c93fc6b4..70d5797a8 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -236,6 +236,13 @@ based on: 'recalbox-multi' by the Recalbox community 0.873 0.212 right + + 0.873 0.212 + 0.1 0.3 + 0 0 + row + favorite completed kids broken + From 6b727e3883e9ca456dd02e95b6267c55c2a39ae2 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sat, 4 Sep 2021 21:15:14 +0200 Subject: [PATCH 002/128] add badges definition to theme interpreter --- es-core/src/ThemeData.cpp | 20 ++++++++++++++++++-- themes/rbsimple-DE/theme.xml | 4 ++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 5de869eb5..13ec5fc4c 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -146,6 +146,15 @@ std::map> The {"unfilledPath", PATH}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, + {"badges", + {{"pos", NORMALIZED_PAIR}, + {"size", NORMALIZED_PAIR}, + {"origin", NORMALIZED_PAIR}, + {"direction", STRING}, + {"slots", STRING}, + {"customBadgeIcon", PATH}, + {"visible", BOOLEAN}, + {"zIndex", FLOAT}}}, {"sound", {{"path", PATH}}}, {"helpsystem", {{"pos", NORMALIZED_PAIR}, @@ -503,8 +512,8 @@ void ThemeData::parseElement(const pugi::xml_node& root, ""); } - // Special parsing instruction for customButtonIcon -> save node as it's button - // attribute to prevent nodes overwriting each other. + // Special parsing instruction for recurring options. + // Store as it's attribute to prevent nodes overwriting each other. if (strcmp(node.name(), "customButtonIcon") == 0) { const auto btn = node.attribute("button").as_string(""); if (strcmp(btn, "") == 0) @@ -513,6 +522,13 @@ void ThemeData::parseElement(const pugi::xml_node& root, else element.properties[btn] = path; } + else if (strcmp(node.name(), "customBadgeIcon") == 0) { + const auto btn = node.attribute("badge").as_string(""); + if (strcmp(btn, "") == 0) + LOG(LogError) << " element requires the `badge` property."; + else + element.properties[btn] = path; + } else element.properties[node.name()] = path; diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 70d5797a8..6e26b63b5 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -242,6 +242,10 @@ based on: 'recalbox-multi' by the Recalbox community 0 0 row favorite completed kids broken + :/graphics/star_filled.svg + :/graphics/star_filled.svg + :/graphics/star_filled.svg + :/graphics/star_filled.svg From fe413bb68fd7ef3a9bafefdaf11571dea78b5f20 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 5 Sep 2021 03:40:23 +0200 Subject: [PATCH 003/128] Initial implementation, including flexbox layout for badges. --- .../views/gamelist/DetailedGameListView.cpp | 2 + .../src/views/gamelist/DetailedGameListView.h | 2 + .../src/views/gamelist/GridGameListView.cpp | 2 + es-app/src/views/gamelist/GridGameListView.h | 2 + .../src/views/gamelist/VideoGameListView.cpp | 31 +- es-app/src/views/gamelist/VideoGameListView.h | 2 + es-core/CMakeLists.txt | 2 + es-core/src/ThemeData.cpp | 4 + es-core/src/ThemeData.h | 1 + es-core/src/components/BadgesComponent.cpp | 381 ++++++++++++++++++ es-core/src/components/BadgesComponent.h | 88 ++++ resources/graphics/badge_broken.png | Bin 0 -> 34510 bytes resources/graphics/badge_completed.png | Bin 0 -> 31616 bytes resources/graphics/badge_favorite.png | Bin 0 -> 31276 bytes resources/graphics/badge_kidgame.png | Bin 0 -> 37035 bytes themes/rbsimple-DE/theme.xml | 15 +- 16 files changed, 523 insertions(+), 9 deletions(-) create mode 100644 es-core/src/components/BadgesComponent.cpp create mode 100644 es-core/src/components/BadgesComponent.h create mode 100644 resources/graphics/badge_broken.png create mode 100644 resources/graphics/badge_completed.png create mode 100644 resources/graphics/badge_favorite.png create mode 100644 resources/graphics/badge_kidgame.png diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 00bb64fed..6d1c5f13d 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -32,6 +32,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) , mLblPlayers(window) , mLblLastPlayed(window) , mLblPlayCount(window) + , mBadges(window) , mRating(window) , mReleaseDate(window) , mDeveloper(window) @@ -75,6 +76,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) addChild(&mImage); // Metadata labels + values. + addChild(&mBadges); mLblRating.setText("Rating: "); addChild(&mLblRating); addChild(&mRating); diff --git a/es-app/src/views/gamelist/DetailedGameListView.h b/es-app/src/views/gamelist/DetailedGameListView.h index f0b0202a9..356d86179 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.h +++ b/es-app/src/views/gamelist/DetailedGameListView.h @@ -9,6 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H +#include "components/BadgesComponent.h" #include "components/DateTimeComponent.h" #include "components/RatingComponent.h" #include "components/ScrollableContainer.h" @@ -46,6 +47,7 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; + BadgesComponent mBadges; RatingComponent mRating; DateTimeComponent mReleaseDate; TextComponent mDeveloper; diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index ae7eae6ee..3db148ade 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -35,6 +35,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) , mLblPlayers(window) , mLblLastPlayed(window) , mLblPlayCount(window) + , mBadges(window) , mRating(window) , mReleaseDate(window) , mDeveloper(window) @@ -55,6 +56,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) populateList(root->getChildrenListToDisplay(), root); // Metadata labels + values. + addChild(&mBadges); mLblRating.setText("Rating: "); addChild(&mLblRating); addChild(&mRating); diff --git a/es-app/src/views/gamelist/GridGameListView.h b/es-app/src/views/gamelist/GridGameListView.h index f7a46ea6a..1f9b648f1 100644 --- a/es-app/src/views/gamelist/GridGameListView.h +++ b/es-app/src/views/gamelist/GridGameListView.h @@ -9,6 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H +#include "components/BadgesComponent.h" #include "components/DateTimeComponent.h" #include "components/ImageGridComponent.h" #include "components/RatingComponent.h" @@ -85,6 +86,7 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; + BadgesComponent mBadges; ImageComponent mMarquee; ImageComponent mImage; RatingComponent mRating; diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 669a3f868..2d2f4faa6 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -46,6 +46,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) , mPublisher(window) , mGenre(window) , mPlayers(window) + , mBadges(window) , mLastPlayed(window) , mPlayCount(window) , mName(window) @@ -110,6 +111,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) mLblPlayers.setText("Players: "); addChild(&mLblPlayers); addChild(&mPlayers); + addChild(&mBadges); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); mLastPlayed.setDisplayRelative(true); @@ -162,6 +164,8 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) mVideo->applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE); + mBadges.applyTheme(theme, getName(), "md_badges", + POSITION | ThemeFlags::SIZE | Z_INDEX | DIRECTION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); initMDLabels(); @@ -176,10 +180,10 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) initMDValues(); std::vector values = getMDValues(); - assert(values.size() == 8); - std::vector valElements = {"md_rating", "md_releasedate", "md_developer", - "md_publisher", "md_genre", "md_players", - "md_lastplayed", "md_playcount"}; + assert(values.size() == 9); + std::vector valElements = {"md_rating", "md_releasedate", "md_developer", + "md_publisher", "md_genre", "md_players", + "md_badges", "md_lastplayed", "md_playcount"}; for (unsigned int i = 0; i < values.size(); i++) values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); @@ -243,6 +247,10 @@ void VideoGameListView::initMDValues() mPublisher.setFont(defaultFont); mGenre.setFont(defaultFont); mPlayers.setFont(defaultFont); + + // TODO: Set appropriate default height. + mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast(defaultFont->getHeight())); + mLastPlayed.setFont(defaultFont); mPlayCount.setFont(defaultFont); @@ -315,6 +323,7 @@ void VideoGameListView::updateInfoPanel() mGenre.setVisible(false); mLblPlayers.setVisible(false); mPlayers.setVisible(false); + mBadges.setVisible(false); mLblLastPlayed.setVisible(false); mLastPlayed.setVisible(false); mLblPlayCount.setVisible(false); @@ -333,6 +342,7 @@ void VideoGameListView::updateInfoPanel() mGenre.setVisible(true); mLblPlayers.setVisible(true); mPlayers.setVisible(true); + mBadges.setVisible(true); mLblLastPlayed.setVisible(true); mLastPlayed.setVisible(true); mLblPlayCount.setVisible(true); @@ -437,6 +447,18 @@ void VideoGameListView::updateInfoPanel() mPublisher.setValue(file->metadata.get("publisher")); mGenre.setValue(file->metadata.get("genre")); mPlayers.setValue(file->metadata.get("players")); + + // Generate badges slots value based on the game metadata. + std::stringstream ss; + ss << (file->metadata.get("favorite").compare("true") ? "favorite " : ""); + ss << (file->metadata.get("completed").compare("true") ? "completed " : ""); + ss << (file->metadata.get("kidgame").compare("true") ? "kidgame " : ""); + ss << (file->metadata.get("broken").compare("true") ? "broken " : ""); + std::string slots = ss.str(); + if (!slots.empty()) + slots.pop_back(); + mBadges.setValue(slots); + mName.setValue(file->metadata.get("name")); if (file->getType() == GAME) { @@ -504,6 +526,7 @@ std::vector VideoGameListView::getMDValues() ret.push_back(&mPublisher); ret.push_back(&mGenre); ret.push_back(&mPlayers); + ret.push_back(&mBadges); ret.push_back(&mLastPlayed); ret.push_back(&mPlayCount); return ret; diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index 4f6988d1f..19c585188 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -9,6 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H +#include "components/BadgesComponent.h" #include "components/DateTimeComponent.h" #include "components/RatingComponent.h" #include "components/ScrollableContainer.h" @@ -50,6 +51,7 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; + BadgesComponent mBadges; RatingComponent mRating; DateTimeComponent mReleaseDate; TextComponent mDeveloper; diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index fb1959b8a..7dcbe1721 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -34,6 +34,7 @@ set(CORE_HEADERS # GUI components ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h @@ -108,6 +109,7 @@ set(CORE_SOURCES # GUI components ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 13ec5fc4c..37c3fae73 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -151,6 +151,10 @@ std::map> The {"size", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, {"direction", STRING}, + {"wrap", STRING}, + {"justifyContent", STRING}, + {"align", STRING}, + {"margin", NORMALIZED_PAIR}, {"slots", STRING}, {"customBadgeIcon", PATH}, {"visible", BOOLEAN}, diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index 181c215a2..9370a10c6 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -53,6 +53,7 @@ namespace ThemeFlags Z_INDEX = 8192, ROTATION = 16384, VISIBLE = 32768, + DIRECTION = 65536, ALL = 0xFFFFFFFF }; } diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp new file mode 100644 index 000000000..a8ac67636 --- /dev/null +++ b/es-core/src/components/BadgesComponent.cpp @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// BadgesComponent.cpp +// +// Game badges icons. +// Used by gamelist views. +// + +#include "components/BadgesComponent.h" +#include + +#include "Settings.h" +#include "ThemeData.h" +#include "resources/TextureResource.h" + +BadgesComponent::BadgesComponent(Window* window) + : GuiComponent(window) + , mDirection(DEFAULT_DIRECTION) + , mWrap(DEFAULT_WRAP) + , mJustifyContent(DEFAULT_JUSTIFY_CONTENT) + , mAlign(DEFAULT_ALIGN) +{ + mSlots = std::vector(); + mSlots.push_back(SLOT_FAVORITE); + mSlots.push_back(SLOT_COMPLETED); + mSlots.push_back(SLOT_KIDS); + mSlots.push_back(SLOT_BROKEN); + + mBadgeIcons = std::map(); + mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.png"; + mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.png"; + mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.png"; + mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.png"; + + mTextures = std::map>(); + mTextures[SLOT_FAVORITE] = TextureResource::get(mBadgeIcons[SLOT_FAVORITE], true); + mTextures[SLOT_COMPLETED] = TextureResource::get(mBadgeIcons[SLOT_COMPLETED], true); + mTextures[SLOT_KIDS] = TextureResource::get(mBadgeIcons[SLOT_KIDS], true); + mTextures[SLOT_BROKEN] = TextureResource::get(mBadgeIcons[SLOT_BROKEN], true); + + mVertices = std::map(); + + // TODO: Should be dependent on the direction property. + mSize = glm::vec2{64.0f * NUM_SLOTS, 64.0f}; + + // TODO: Add definition for default value. + mMargin = glm::vec2{10.0f, 10.0f}; + + updateVertices(); +} + +void BadgesComponent::setValue(const std::string& value) +{ + if (value.empty()) { + mSlots.clear(); + } + else { + // Start by clearing the slots. + mSlots.clear(); + + // Interpret the value and iteratively fill mSlots. The value is a space separated list of + // strings. + std::string temp; + std::istringstream ss(value); + while (std::getline(ss, temp, ' ')) { + if (!(temp == SLOT_FAVORITE || temp == SLOT_COMPLETED || temp == SLOT_KIDS || + temp == SLOT_BROKEN)) + LOG(LogError) << "Badge slot '" << temp << "' is invalid."; + else + mSlots.push_back(temp); + } + } + + updateVertices(); +} + +std::string BadgesComponent::getValue() const +{ + std::stringstream ss; + for (auto& slot : mSlots) + ss << slot << ' '; + std::string r = ss.str(); + r.pop_back(); + return r; +} + +void BadgesComponent::onSizeChanged() +{ + // TODO: Should be dependent on the direction property. + if (mSize.y == 0.0f) + mSize.y = mSize.x / NUM_SLOTS; + else if (mSize.x == 0.0f) + mSize.x = mSize.y * NUM_SLOTS; + + if (mSize.y > 0.0f) { + size_t heightPx = static_cast(std::round(mSize.y)); + for (auto const& tex : mTextures) + tex.second->rasterizeAt(heightPx, heightPx); + } + + updateVertices(); +} + +void BadgesComponent::updateVertices() +{ + mVertices.clear(); + + /*const float numSlots = mSlots.size(); + float s; + if (mDirection == DIRECTION_ROW) + s = std::min( getSize().x / numSlots, getSize().y ); + else + s = std::min( getSize().y / numSlots, getSize().x ); + const long color = 4278190080; + + int i = 0; + for (auto & slot : mSlots) + { + // clang-format off + mVertices[slot][0] = {{0.0f, 0.0f}, {0.0f, 1.0f}, color}; + mVertices[slot][1] = {{0.0f, s}, {0.0f, 0.0f}, color}; + mVertices[slot][2] = {{s , 0.0f}, {1.0f, 1.0f}, color}; + mVertices[slot][3] = {{s , s}, {1.0f, 0.0f}, color}; + // clang-format on + i++; + }*/ + + // The maximum number of badges to be displayed. + const float numSlots = NUM_SLOTS; + + // The available size to draw in. + const auto size = getSize(); + + // Compute the number of rows and columns and the item max dimensions. + int rows; + int columns; + float itemWidth; + float itemHeight; + + if (mDirection == DIRECTION_ROW) { + if (mWrap != WRAP_NOWRAP) { + // Suppose we have i rows, what would be the average area of an icon? Compute for a + // small number of rows. + std::vector areas; + for (int i = 1; i < 10; i++) { + + float area = size.x * size.y; + + // Number of vertical gaps. + int verticalGaps = i - 1; + + // Area of vertical gaps. + area -= verticalGaps * mMargin.y * size.x; + + // Height per item. + float iHeight = (size.y - verticalGaps * mMargin.y) / i; + + // Width per item. (Approximation) + // TODO: this is an approximation! + // Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight = + // 0; + float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x; + + // Average area available per badge + float avgArea = iHeight * iWidth; + + // Push to the areas array. + areas.push_back(avgArea); + } + + // Determine the number of rows based on what results in the largest area per badge + // based on available space. + rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1; + + // Obtain final item dimensions. + itemHeight = (size.y - (rows - 1) * mMargin.y) / rows; + itemWidth = areas[rows - 1] / itemHeight; + + // Compute number of columns. + if (rows == 1) + columns = NUM_SLOTS; + else + columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x)); + } + else { + rows = 1; + columns = NUM_SLOTS; + itemHeight = size.y; + itemWidth = size.x / (NUM_SLOTS + (NUM_SLOTS - 1) * mMargin.x); + } + } + else { + // TODO: Add computation for column direction. + } + + const long color = 4278190080; + if (mDirection == DIRECTION_ROW) { + + // Start row. + int row = mWrap == WRAP_REVERSE ? rows : 1; + int item = 0; + + // Iterate through all the rows. + for (int c = 0; c < rows && item < mSlots.size(); c++) { + + // Pre-compute dimensions of all items in this row. + std::vector widths; + std::vector heights; + int itemTemp = item; + for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) { + glm::vec texSize = mTextures[mSlots[itemTemp]]->getSize(); + float aspectRatioTexture = texSize.x / texSize.y; + float aspectRatioItemSpace = itemWidth / itemHeight; + if (aspectRatioTexture > aspectRatioItemSpace) { + widths.push_back(itemWidth); + heights.push_back(itemWidth / aspectRatioTexture); + } + else { + widths.push_back(itemHeight * aspectRatioTexture); + heights.push_back(itemHeight); + } + itemTemp++; + } + + // Iterate through the columns. + float xpos = 0; + for (int column = 0; column < columns && item < mSlots.size(); column++) { + + // We always go from left to right. + // Here we compute the coordinates of the items. + + // Compute final badge x position. + float x; + float totalWidth = + std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) + + (widths.size() - 1) * mMargin.x; + if (mJustifyContent == "start") { + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "end") { + if (column == 0) + xpos += size.x - totalWidth; + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "center") { + if (column == 0) + xpos += (size.x - totalWidth) / 2; + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "space-between") { + float gapSize = (size.x - totalWidth) / (widths.size() - 1); + x = xpos; + xpos += widths[column] + gapSize; + } + else if (mJustifyContent == "space-around") { + float gapSize = (size.x - totalWidth) / (widths.size() - 1); + xpos += gapSize / 2; + x = xpos; + xpos += widths[column] + gapSize / 2; + } + else if (mJustifyContent == "space-evenly") { + float gapSize = (size.x - totalWidth) / (widths.size() + 1); + xpos += gapSize; + x = xpos; + } + + // Compute final badge y position. + float y = row * itemHeight; + if (mAlign == "end") { + y += itemHeight - heights[column]; + } + else if (mAlign == "center") { + y += (itemHeight - heights[column]) / 2; + } + if (mAlign == "stretch") { + heights[column] = itemHeight; + } + + LOG(LogError) << "Computed Final Item Position. Row: " << row + << ", Column: " << column << ", Item: " << item << ", pos: (" << x + << ", " << y << "), size: (" << widths[column] << ", " + << heights[column] << ")"; + + // Store the item's vertices and apply texture mapping. + // clang-format off + mVertices[mSlots[item]][0] = {{x, y}, {0.0f, 1.0f}, color}; + mVertices[mSlots[item]][1] = {{x, y+heights[column]}, {0.0f, 0.0f}, color}; + mVertices[mSlots[item]][2] = {{x+widths[column] , y}, {1.0f, 1.0f}, color}; + mVertices[mSlots[item]][3] = {{x+widths[column] , y+heights[column]}, {1.0f, 0.0f}, color}; + // clang-format on + + // Increment item; + item++; + } + + // Iterate the row. + mWrap == WRAP_REVERSE ? row-- : row++; + } + } +} + +void BadgesComponent::render(const glm::mat4& parentTrans) +{ + if (!isVisible()) + return; + + glm::mat4 trans{parentTrans * getTransform()}; + + Renderer::setMatrix(trans); + + if (mOpacity > 0) { + if (Settings::getInstance()->getBool("DebugImage")) + Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); + + for (auto& slot : mSlots) { + if (mTextures[slot] == nullptr) + continue; + + if (mTextures[slot]->bind()) { + Renderer::drawTriangleStrips(mVertices[slot], 4); + Renderer::bindTexture(0); + } + + // TODO: update render matrix to position of next slot + // trans = glm::translate(trans, {0.0f, 0.0f, 1.0f}); + } + } + + renderChildren(trans); +} + +void BadgesComponent::applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) +{ + using namespace ThemeFlags; + + const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); + if (!elem) + return; + + bool imgChanged = false; + for (auto& slot : mSlots) { + if (properties & PATH && elem->has(slot)) { + mBadgeIcons[slot] = elem->get(slot); + mTextures[slot] = TextureResource::get(mBadgeIcons[slot], true); + imgChanged = true; + } + } + + if (properties & DIRECTION && elem->has("direction")) + mDirection = elem->get("direction"); + + if (elem->has("wrap")) + mWrap = elem->get("wrap"); + + if (elem->has("justifyContent")) + mJustifyContent = elem->get("justifyContent"); + + if (elem->has("align")) + mAlign = elem->get("align"); + + if (elem->has("slots")) + setValue(elem->get("slots")); + + GuiComponent::applyTheme(theme, view, element, properties); + + if (imgChanged) + onSizeChanged(); +} + +std::vector BadgesComponent::getHelpPrompts() +{ + std::vector prompts; + return prompts; +} diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h new file mode 100644 index 000000000..906463cca --- /dev/null +++ b/es-core/src/components/BadgesComponent.h @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// BadgesComponent.h +// +// Game badges icons. +// Used by gamelist views. +// + +#ifndef ES_APP_COMPONENTS_BADGES_COMPONENT_H +#define ES_APP_COMPONENTS_BADGES_COMPONENT_H + +#include "GuiComponent.h" +#include "renderers/Renderer.h" + +#define DIRECTION_ROW "row" +#define DIRECTION_COLUMN "column" +#define WRAP_WRAP "wrap" +#define WRAP_NOWRAP "nowrap" +#define WRAP_REVERSE "wrap-reverse" +#define JUSTIFY_CONTENT_START "start" +#define JUSTIFY_CONTENT_END "end" +#define JUSTIFY_CONTENT_CENTER "center" +#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between" +#define JUSTIFY_CONTENT_SPACE_AROUND "space-around" +#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly" +#define ITEM_ALIGN_START "start" +#define ITEM_ALIGN_END "end" +#define ITEM_ALIGN_CENTER "center" +#define ITEM_ALIGN_STRETCH "stretch" +#define NUM_SLOTS 4 +#define SLOT_FAVORITE "favorite" +#define SLOT_COMPLETED "completed" +#define SLOT_KIDS "kidgame" +#define SLOT_BROKEN "broken" +#define DEFAULT_DIRECTION DIRECTION_ROW +#define DEFAULT_WRAP WRAP_WRAP +#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START +#define DEFAULT_ALIGN ITEM_ALIGN_CENTER +#define DEFAULT_MARGIN_X = 10.0f +#define DEFAULT_MARGIN_Y = 10.0f + +class TextureResource; + +class BadgesComponent : public GuiComponent +{ +public: + BadgesComponent(Window* window); + + std::string getValue() const override; + // Should be a list of strings. + void setValue(const std::string& value) override; + + void render(const glm::mat4& parentTrans) override; + + void onSizeChanged() override; + + void setDirection(int direction); + + int getDirection(); + + void setSlots(std::vector); + + std::vector getSlots(); + + virtual void applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) override; + + virtual std::vector getHelpPrompts() override; + +private: + void updateVertices(); + std::map mVertices; + + std::map mBadgeIcons; + std::map> mTextures; + + std::string mDirection; + std::string mWrap; + std::string mJustifyContent; + std::string mAlign; + glm::vec2 mMargin; + std::vector mSlots; +}; + +#endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H diff --git a/resources/graphics/badge_broken.png b/resources/graphics/badge_broken.png new file mode 100644 index 0000000000000000000000000000000000000000..ae97d4c8bf31e4d7b5d717c23a080e812f1b925d GIT binary patch literal 34510 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 Rf#IXLa#`+Ds)X)?{{{F&BLx5e literal 0 HcmV?d00001 diff --git a/resources/graphics/badge_favorite.png b/resources/graphics/badge_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb8be1ac9a41858717ad26f30a25835eeecd6ac GIT binary patch literal 31276 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 DyG>Rt literal 0 HcmV?d00001 diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 6e26b63b5..dd6027f9a 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -241,11 +241,16 @@ based on: 'recalbox-multi' by the Recalbox community 0.1 0.3 0 0 row - favorite completed kids broken - :/graphics/star_filled.svg - :/graphics/star_filled.svg - :/graphics/star_filled.svg - :/graphics/star_filled.svg + wrap + start + + start + 20 10 + favorite completed kidgame broken + :/graphics/badge_favorite.png + :/graphics/badge_completed.png + :/graphics/badge_kidgame.png + :/graphics/badge_broken.png From efe928852f303c9e98e50a5ab9fd2d870d3a0420 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Tue, 7 Sep 2021 17:21:54 +0200 Subject: [PATCH 004/128] Separate flexbox functionality in it's own component. --- es-core/CMakeLists.txt | 2 + es-core/src/components/BadgesComponent.cpp | 327 ++------------------ es-core/src/components/BadgesComponent.h | 49 +-- es-core/src/components/FlexboxComponent.cpp | 281 +++++++++++++++++ es-core/src/components/FlexboxComponent.h | 85 +++++ 5 files changed, 404 insertions(+), 340 deletions(-) create mode 100644 es-core/src/components/FlexboxComponent.cpp create mode 100644 es-core/src/components/FlexboxComponent.h diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index 7dcbe1721..d5fa9dd71 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -41,6 +41,7 @@ set(CORE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h @@ -116,6 +117,7 @@ set(CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index a8ac67636..b65b0c5d5 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -8,24 +8,16 @@ // #include "components/BadgesComponent.h" -#include #include "Settings.h" #include "ThemeData.h" #include "resources/TextureResource.h" BadgesComponent::BadgesComponent(Window* window) - : GuiComponent(window) - , mDirection(DEFAULT_DIRECTION) - , mWrap(DEFAULT_WRAP) - , mJustifyContent(DEFAULT_JUSTIFY_CONTENT) - , mAlign(DEFAULT_ALIGN) + : FlexboxComponent(window, NUM_SLOTS) { - mSlots = std::vector(); - mSlots.push_back(SLOT_FAVORITE); - mSlots.push_back(SLOT_COMPLETED); - mSlots.push_back(SLOT_KIDS); - mSlots.push_back(SLOT_BROKEN); + // Define the slots. + setSlots({SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}); mBadgeIcons = std::map(); mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.png"; @@ -33,33 +25,34 @@ BadgesComponent::BadgesComponent(Window* window) mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.png"; mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.png"; - mTextures = std::map>(); - mTextures[SLOT_FAVORITE] = TextureResource::get(mBadgeIcons[SLOT_FAVORITE], true); - mTextures[SLOT_COMPLETED] = TextureResource::get(mBadgeIcons[SLOT_COMPLETED], true); - mTextures[SLOT_KIDS] = TextureResource::get(mBadgeIcons[SLOT_KIDS], true); - mTextures[SLOT_BROKEN] = TextureResource::get(mBadgeIcons[SLOT_BROKEN], true); - - mVertices = std::map(); + // Create the child ImageComponent for every badge. + mImageComponents = std::map(); + ImageComponent mImageFavorite = ImageComponent(window); + mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, false); + mImageComponents.insert({SLOT_FAVORITE, mImageFavorite}); + ImageComponent mImageCompleted = ImageComponent(window); + mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, false); + mImageComponents.insert({SLOT_COMPLETED, mImageCompleted}); + ImageComponent mImageKids = ImageComponent(window); + mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, false); + mImageComponents.insert({SLOT_KIDS, mImageKids}); + ImageComponent mImageBroken = ImageComponent(window); + mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, false); + mImageComponents.insert({SLOT_BROKEN, mImageBroken}); // TODO: Should be dependent on the direction property. mSize = glm::vec2{64.0f * NUM_SLOTS, 64.0f}; - // TODO: Add definition for default value. - mMargin = glm::vec2{10.0f, 10.0f}; - - updateVertices(); + // Trigger initial layout computation. + onSizeChanged(); } void BadgesComponent::setValue(const std::string& value) { - if (value.empty()) { - mSlots.clear(); - } - else { - // Start by clearing the slots. - mSlots.clear(); + std::vector slots = {}; - // Interpret the value and iteratively fill mSlots. The value is a space separated list of + if (!value.empty()) { + // Interpret the value and iteratively fill slots. The value is a space separated list of // strings. std::string temp; std::istringstream ss(value); @@ -68,271 +61,25 @@ void BadgesComponent::setValue(const std::string& value) temp == SLOT_BROKEN)) LOG(LogError) << "Badge slot '" << temp << "' is invalid."; else - mSlots.push_back(temp); + slots.push_back(temp); } } - updateVertices(); + setSlots(slots); + onSizeChanged(); } std::string BadgesComponent::getValue() const { + const std::vector slots = getSlots(); std::stringstream ss; - for (auto& slot : mSlots) + for (auto& slot : slots) ss << slot << ' '; std::string r = ss.str(); r.pop_back(); return r; } -void BadgesComponent::onSizeChanged() -{ - // TODO: Should be dependent on the direction property. - if (mSize.y == 0.0f) - mSize.y = mSize.x / NUM_SLOTS; - else if (mSize.x == 0.0f) - mSize.x = mSize.y * NUM_SLOTS; - - if (mSize.y > 0.0f) { - size_t heightPx = static_cast(std::round(mSize.y)); - for (auto const& tex : mTextures) - tex.second->rasterizeAt(heightPx, heightPx); - } - - updateVertices(); -} - -void BadgesComponent::updateVertices() -{ - mVertices.clear(); - - /*const float numSlots = mSlots.size(); - float s; - if (mDirection == DIRECTION_ROW) - s = std::min( getSize().x / numSlots, getSize().y ); - else - s = std::min( getSize().y / numSlots, getSize().x ); - const long color = 4278190080; - - int i = 0; - for (auto & slot : mSlots) - { - // clang-format off - mVertices[slot][0] = {{0.0f, 0.0f}, {0.0f, 1.0f}, color}; - mVertices[slot][1] = {{0.0f, s}, {0.0f, 0.0f}, color}; - mVertices[slot][2] = {{s , 0.0f}, {1.0f, 1.0f}, color}; - mVertices[slot][3] = {{s , s}, {1.0f, 0.0f}, color}; - // clang-format on - i++; - }*/ - - // The maximum number of badges to be displayed. - const float numSlots = NUM_SLOTS; - - // The available size to draw in. - const auto size = getSize(); - - // Compute the number of rows and columns and the item max dimensions. - int rows; - int columns; - float itemWidth; - float itemHeight; - - if (mDirection == DIRECTION_ROW) { - if (mWrap != WRAP_NOWRAP) { - // Suppose we have i rows, what would be the average area of an icon? Compute for a - // small number of rows. - std::vector areas; - for (int i = 1; i < 10; i++) { - - float area = size.x * size.y; - - // Number of vertical gaps. - int verticalGaps = i - 1; - - // Area of vertical gaps. - area -= verticalGaps * mMargin.y * size.x; - - // Height per item. - float iHeight = (size.y - verticalGaps * mMargin.y) / i; - - // Width per item. (Approximation) - // TODO: this is an approximation! - // Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight = - // 0; - float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x; - - // Average area available per badge - float avgArea = iHeight * iWidth; - - // Push to the areas array. - areas.push_back(avgArea); - } - - // Determine the number of rows based on what results in the largest area per badge - // based on available space. - rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1; - - // Obtain final item dimensions. - itemHeight = (size.y - (rows - 1) * mMargin.y) / rows; - itemWidth = areas[rows - 1] / itemHeight; - - // Compute number of columns. - if (rows == 1) - columns = NUM_SLOTS; - else - columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x)); - } - else { - rows = 1; - columns = NUM_SLOTS; - itemHeight = size.y; - itemWidth = size.x / (NUM_SLOTS + (NUM_SLOTS - 1) * mMargin.x); - } - } - else { - // TODO: Add computation for column direction. - } - - const long color = 4278190080; - if (mDirection == DIRECTION_ROW) { - - // Start row. - int row = mWrap == WRAP_REVERSE ? rows : 1; - int item = 0; - - // Iterate through all the rows. - for (int c = 0; c < rows && item < mSlots.size(); c++) { - - // Pre-compute dimensions of all items in this row. - std::vector widths; - std::vector heights; - int itemTemp = item; - for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) { - glm::vec texSize = mTextures[mSlots[itemTemp]]->getSize(); - float aspectRatioTexture = texSize.x / texSize.y; - float aspectRatioItemSpace = itemWidth / itemHeight; - if (aspectRatioTexture > aspectRatioItemSpace) { - widths.push_back(itemWidth); - heights.push_back(itemWidth / aspectRatioTexture); - } - else { - widths.push_back(itemHeight * aspectRatioTexture); - heights.push_back(itemHeight); - } - itemTemp++; - } - - // Iterate through the columns. - float xpos = 0; - for (int column = 0; column < columns && item < mSlots.size(); column++) { - - // We always go from left to right. - // Here we compute the coordinates of the items. - - // Compute final badge x position. - float x; - float totalWidth = - std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) + - (widths.size() - 1) * mMargin.x; - if (mJustifyContent == "start") { - x = xpos; - xpos += widths[column] + mMargin.x; - } - else if (mJustifyContent == "end") { - if (column == 0) - xpos += size.x - totalWidth; - x = xpos; - xpos += widths[column] + mMargin.x; - } - else if (mJustifyContent == "center") { - if (column == 0) - xpos += (size.x - totalWidth) / 2; - x = xpos; - xpos += widths[column] + mMargin.x; - } - else if (mJustifyContent == "space-between") { - float gapSize = (size.x - totalWidth) / (widths.size() - 1); - x = xpos; - xpos += widths[column] + gapSize; - } - else if (mJustifyContent == "space-around") { - float gapSize = (size.x - totalWidth) / (widths.size() - 1); - xpos += gapSize / 2; - x = xpos; - xpos += widths[column] + gapSize / 2; - } - else if (mJustifyContent == "space-evenly") { - float gapSize = (size.x - totalWidth) / (widths.size() + 1); - xpos += gapSize; - x = xpos; - } - - // Compute final badge y position. - float y = row * itemHeight; - if (mAlign == "end") { - y += itemHeight - heights[column]; - } - else if (mAlign == "center") { - y += (itemHeight - heights[column]) / 2; - } - if (mAlign == "stretch") { - heights[column] = itemHeight; - } - - LOG(LogError) << "Computed Final Item Position. Row: " << row - << ", Column: " << column << ", Item: " << item << ", pos: (" << x - << ", " << y << "), size: (" << widths[column] << ", " - << heights[column] << ")"; - - // Store the item's vertices and apply texture mapping. - // clang-format off - mVertices[mSlots[item]][0] = {{x, y}, {0.0f, 1.0f}, color}; - mVertices[mSlots[item]][1] = {{x, y+heights[column]}, {0.0f, 0.0f}, color}; - mVertices[mSlots[item]][2] = {{x+widths[column] , y}, {1.0f, 1.0f}, color}; - mVertices[mSlots[item]][3] = {{x+widths[column] , y+heights[column]}, {1.0f, 0.0f}, color}; - // clang-format on - - // Increment item; - item++; - } - - // Iterate the row. - mWrap == WRAP_REVERSE ? row-- : row++; - } - } -} - -void BadgesComponent::render(const glm::mat4& parentTrans) -{ - if (!isVisible()) - return; - - glm::mat4 trans{parentTrans * getTransform()}; - - Renderer::setMatrix(trans); - - if (mOpacity > 0) { - if (Settings::getInstance()->getBool("DebugImage")) - Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); - - for (auto& slot : mSlots) { - if (mTextures[slot] == nullptr) - continue; - - if (mTextures[slot]->bind()) { - Renderer::drawTriangleStrips(mVertices[slot], 4); - Renderer::bindTexture(0); - } - - // TODO: update render matrix to position of next slot - // trans = glm::translate(trans, {0.0f, 0.0f, 1.0f}); - } - } - - renderChildren(trans); -} - void BadgesComponent::applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, @@ -345,30 +92,20 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, return; bool imgChanged = false; - for (auto& slot : mSlots) { + const std::vector slots = getSlots(); + for (auto& slot : slots) { if (properties & PATH && elem->has(slot)) { mBadgeIcons[slot] = elem->get(slot); - mTextures[slot] = TextureResource::get(mBadgeIcons[slot], true); + mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]); imgChanged = true; } } - if (properties & DIRECTION && elem->has("direction")) - mDirection = elem->get("direction"); - - if (elem->has("wrap")) - mWrap = elem->get("wrap"); - - if (elem->has("justifyContent")) - mJustifyContent = elem->get("justifyContent"); - - if (elem->has("align")) - mAlign = elem->get("align"); - if (elem->has("slots")) setValue(elem->get("slots")); - GuiComponent::applyTheme(theme, view, element, properties); + // Apply theme on the flexbox component parent. + FlexboxComponent::applyTheme(theme, view, element, properties); if (imgChanged) onSizeChanged(); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 906463cca..715714057 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -10,39 +10,20 @@ #ifndef ES_APP_COMPONENTS_BADGES_COMPONENT_H #define ES_APP_COMPONENTS_BADGES_COMPONENT_H +#include "FlexboxComponent.h" #include "GuiComponent.h" +#include "ImageComponent.h" #include "renderers/Renderer.h" -#define DIRECTION_ROW "row" -#define DIRECTION_COLUMN "column" -#define WRAP_WRAP "wrap" -#define WRAP_NOWRAP "nowrap" -#define WRAP_REVERSE "wrap-reverse" -#define JUSTIFY_CONTENT_START "start" -#define JUSTIFY_CONTENT_END "end" -#define JUSTIFY_CONTENT_CENTER "center" -#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between" -#define JUSTIFY_CONTENT_SPACE_AROUND "space-around" -#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly" -#define ITEM_ALIGN_START "start" -#define ITEM_ALIGN_END "end" -#define ITEM_ALIGN_CENTER "center" -#define ITEM_ALIGN_STRETCH "stretch" #define NUM_SLOTS 4 #define SLOT_FAVORITE "favorite" #define SLOT_COMPLETED "completed" #define SLOT_KIDS "kidgame" #define SLOT_BROKEN "broken" -#define DEFAULT_DIRECTION DIRECTION_ROW -#define DEFAULT_WRAP WRAP_WRAP -#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START -#define DEFAULT_ALIGN ITEM_ALIGN_CENTER -#define DEFAULT_MARGIN_X = 10.0f -#define DEFAULT_MARGIN_Y = 10.0f class TextureResource; -class BadgesComponent : public GuiComponent +class BadgesComponent : public FlexboxComponent { public: BadgesComponent(Window* window); @@ -51,18 +32,6 @@ public: // Should be a list of strings. void setValue(const std::string& value) override; - void render(const glm::mat4& parentTrans) override; - - void onSizeChanged() override; - - void setDirection(int direction); - - int getDirection(); - - void setSlots(std::vector); - - std::vector getSlots(); - virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, @@ -71,18 +40,8 @@ public: virtual std::vector getHelpPrompts() override; private: - void updateVertices(); - std::map mVertices; - std::map mBadgeIcons; - std::map> mTextures; - - std::string mDirection; - std::string mWrap; - std::string mJustifyContent; - std::string mAlign; - glm::vec2 mMargin; - std::vector mSlots; + std::map mImageComponents; }; #endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp new file mode 100644 index 000000000..210b2d2f2 --- /dev/null +++ b/es-core/src/components/FlexboxComponent.cpp @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// FlexboxComponent.cpp +// +// Flexbox layout component. +// Used by gamelist views. +// + +#include "components/FlexboxComponent.h" +#include + +#include "Settings.h" +#include "ThemeData.h" +#include "resources/TextureResource.h" + +FlexboxComponent::FlexboxComponent(Window* window, unsigned int assumeChildren) + : GuiComponent(window) + , mDirection(DEFAULT_DIRECTION) + , mWrap(DEFAULT_WRAP) + , mJustifyContent(DEFAULT_JUSTIFY_CONTENT) + , mAlign(DEFAULT_ALIGN) + , mAssumeChildren(assumeChildren) +{ + + // Initialize contents of the flexbox. + mSlots = std::vector(); + mComponents = std::map(); + + // Initialize flexbox layout. + mVertices = std::map(); + + // TODO: Should be dependent on the direction property. + mSize = glm::vec2{64.0f * mAssumeChildren, 64.0f}; + + // TODO: Add definition for default value. + mMargin = glm::vec2{10.0f, 10.0f}; + + // Calculate flexbox layout. + updateVertices(); +} + +void FlexboxComponent::onSizeChanged() +{ + // TODO: Should be dependent on the direction property. + if (mSize.y == 0.0f) + mSize.y = mSize.x / mAssumeChildren; + else if (mSize.x == 0.0f) + mSize.x = mSize.y * mAssumeChildren; + + updateVertices(); +} + +void FlexboxComponent::updateVertices() +{ + // The maximum number of components to be displayed. + const float numSlots = mAssumeChildren; + + // The available size to draw in. + const auto size = getSize(); + + // Compute the number of rows and columns and the item max dimensions. + int rows; + int columns; + float itemWidth; + float itemHeight; + if (mDirection == DIRECTION_ROW) { + if (mWrap != WRAP_NOWRAP) { + // Suppose we have i rows, what would be the average area of an icon? Compute for a + // small number of rows. + std::vector areas; + for (int i = 1; i < 10; i++) { + + float area = size.x * size.y; + + // Number of vertical gaps. + int verticalGaps = i - 1; + + // Area of vertical gaps. + area -= verticalGaps * mMargin.y * size.x; + + // Height per item. + float iHeight = (size.y - verticalGaps * mMargin.y) / i; + + // Width per item. (Approximation) + // TODO: this is an approximation! + // Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight = + // 0; + float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x; + + // Average area available per badge + float avgArea = iHeight * iWidth; + + // Push to the areas array. + areas.push_back(avgArea); + } + + // Determine the number of rows based on what results in the largest area per badge + // based on available space. + rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1; + + // Obtain final item dimensions. + itemHeight = (size.y - (rows - 1) * mMargin.y) / rows; + itemWidth = areas[rows - 1] / itemHeight; + + // Compute number of columns. + if (rows == 1) + columns = mAssumeChildren; + else + columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x)); + } + else { + rows = 1; + columns = mAssumeChildren; + itemHeight = size.y; + itemWidth = size.x / (mAssumeChildren + (mAssumeChildren - 1) * mMargin.x); + } + } + else { + // TODO: Add computation for column direction. + } + + // Compute the exact positions and sizes of the components. + mVertices.clear(); + if (mDirection == DIRECTION_ROW) { + + // Start row. + int row = mWrap == WRAP_REVERSE ? rows : 1; + int item = 0; + + // Iterate through all the rows. + for (int c = 0; c < rows && item < mSlots.size(); c++) { + + // Pre-compute dimensions of all items in this row. + std::vector widths; + std::vector heights; + int itemTemp = item; + for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) { + glm::vec componentSize = mComponents.find(mSlots[itemTemp])->second.getSize(); + float aspectRatioTexture = componentSize.x / componentSize.y; + float aspectRatioItemSpace = itemWidth / itemHeight; + if (aspectRatioTexture > aspectRatioItemSpace) { + widths.push_back(itemWidth); + heights.push_back(itemWidth / aspectRatioTexture); + } + else { + widths.push_back(itemHeight * aspectRatioTexture); + heights.push_back(itemHeight); + } + itemTemp++; + } + + // Iterate through the columns. + float xpos = 0; + for (int column = 0; column < columns && item < mSlots.size(); column++) { + + // We always go from left to right. + // Here we compute the coordinates of the items. + + // Compute final badge x position. + float x; + float totalWidth = + std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) + + (widths.size() - 1) * mMargin.x; + if (mJustifyContent == "start") { + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "end") { + if (column == 0) + xpos += size.x - totalWidth; + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "center") { + if (column == 0) + xpos += (size.x - totalWidth) / 2; + x = xpos; + xpos += widths[column] + mMargin.x; + } + else if (mJustifyContent == "space-between") { + float gapSize = (size.x - totalWidth) / (widths.size() - 1); + x = xpos; + xpos += widths[column] + gapSize; + } + else if (mJustifyContent == "space-around") { + float gapSize = (size.x - totalWidth) / (widths.size() - 1); + xpos += gapSize / 2; + x = xpos; + xpos += widths[column] + gapSize / 2; + } + else if (mJustifyContent == "space-evenly") { + float gapSize = (size.x - totalWidth) / (widths.size() + 1); + xpos += gapSize; + x = xpos; + } + + // Compute final badge y position. + float y = row * itemHeight; + if (mAlign == "end") { + y += itemHeight - heights[column]; + } + else if (mAlign == "center") { + y += (itemHeight - heights[column]) / 2; + } + if (mAlign == "stretch") { + heights[column] = itemHeight; + } + + LOG(LogError) << "Computed Final Item Position. Row: " << row + << ", Column: " << column << ", Item: " << item << ", pos: (" << x + << ", " << y << "), size: (" << widths[column] << ", " + << heights[column] << ")"; + + // Store the item's layout. + mVertices[mSlots[item]] = {x, y, widths[column], heights[column]}; + + // Increment item; + item++; + } + + // Iterate the row. + mWrap == WRAP_REVERSE ? row-- : row++; + } + } +} + +void FlexboxComponent::render(const glm::mat4& parentTrans) +{ + if (!isVisible()) + return; + + // Render all the child components. + for (unsigned int i = 0; i < mSlots.size(); i++) { + glm::vec4 v = mVertices[mSlots[i]]; + auto c = mComponents.find(mSlots[i])->second; + glm::vec2 oldSize = c.getSize(); + c.setPosition(v.x, v.y); + c.setSize(v.z, v.w); + c.render(parentTrans); + c.setSize(oldSize); + } + + renderChildren(parentTrans); +} + +void FlexboxComponent::applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) +{ + using namespace ThemeFlags; + + // TODO: How to do this without explicit 'badges' property? + const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); + if (!elem) + return; + + if (properties & DIRECTION && elem->has("direction")) + mDirection = elem->get("direction"); + + if (elem->has("wrap")) + mWrap = elem->get("wrap"); + + if (elem->has("justifyContent")) + mJustifyContent = elem->get("justifyContent"); + + if (elem->has("align")) + mAlign = elem->get("align"); + + GuiComponent::applyTheme(theme, view, element, properties); + + // Trigger layout computation. + onSizeChanged(); +} + +std::vector FlexboxComponent::getHelpPrompts() +{ + std::vector prompts; + return prompts; +} diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h new file mode 100644 index 000000000..0ae106bd8 --- /dev/null +++ b/es-core/src/components/FlexboxComponent.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// FlexboxComponent.h +// +// Flexbox layout component. +// Used by gamelist views. +// + +#ifndef ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H +#define ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H + +#include "GuiComponent.h" +#include "renderers/Renderer.h" + +#define DIRECTION_ROW "row" +#define DIRECTION_COLUMN "column" +#define WRAP_WRAP "wrap" +#define WRAP_NOWRAP "nowrap" +#define WRAP_REVERSE "wrap-reverse" +#define JUSTIFY_CONTENT_START "start" +#define JUSTIFY_CONTENT_END "end" +#define JUSTIFY_CONTENT_CENTER "center" +#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between" +#define JUSTIFY_CONTENT_SPACE_AROUND "space-around" +#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly" +#define ITEM_ALIGN_START "start" +#define ITEM_ALIGN_END "end" +#define ITEM_ALIGN_CENTER "center" +#define ITEM_ALIGN_STRETCH "stretch" +#define DEFAULT_DIRECTION DIRECTION_ROW +#define DEFAULT_WRAP WRAP_WRAP +#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START +#define DEFAULT_ALIGN ITEM_ALIGN_CENTER +#define DEFAULT_MARGIN_X = 10.0f +#define DEFAULT_MARGIN_Y = 10.0f + +class TextureResource; + +class FlexboxComponent : public GuiComponent +{ +public: + FlexboxComponent(Window* window, unsigned int assumeChildren = 0); + + void render(const glm::mat4& parentTrans) override; + + void onSizeChanged() override; + + void setDirection(int direction); + + int getDirection(); + + void setSlots(std::vector); + + std::vector getSlots() const; + + virtual void applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) override; + + virtual std::vector getHelpPrompts() override; + +private: + // Calculate flexbox layout. + void updateVertices(); + + // Storage for the flexbox components positions and sizes. + std::map mVertices; + + // The components of the flexbox. + std::map mComponents; + + // Named map of the components of the flexbox. + std::vector mSlots; + + std::string mDirection; + std::string mWrap; + std::string mJustifyContent; + std::string mAlign; + glm::vec2 mMargin; + unsigned int mAssumeChildren; +}; + +#endif // ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H From aaf5d0209bf920be2da4894a749e0335f082796f Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Tue, 14 Sep 2021 01:01:46 +0200 Subject: [PATCH 005/128] Implement flexbox and badges. --- es-core/src/ThemeData.cpp | 7 +- es-core/src/components/BadgesComponent.cpp | 24 +- es-core/src/components/BadgesComponent.h | 1 + es-core/src/components/FlexboxComponent.cpp | 313 ++++++++------------ es-core/src/components/FlexboxComponent.h | 65 ++-- themes/rbsimple-DE/theme.xml | 16 +- 6 files changed, 161 insertions(+), 265 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 37c3fae73..8dd5ae90c 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -148,13 +148,12 @@ std::map> The {"zIndex", FLOAT}}}, {"badges", {{"pos", NORMALIZED_PAIR}, - {"size", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, {"direction", STRING}, - {"wrap", STRING}, - {"justifyContent", STRING}, {"align", STRING}, - {"margin", NORMALIZED_PAIR}, + {"itemsPerLine", FLOAT}, + {"itemMargin", NORMALIZED_PAIR}, + {"itemWidth", FLOAT}, {"slots", STRING}, {"customBadgeIcon", PATH}, {"visible", BOOLEAN}, diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index b65b0c5d5..51fb09056 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -14,10 +14,10 @@ #include "resources/TextureResource.h" BadgesComponent::BadgesComponent(Window* window) - : FlexboxComponent(window, NUM_SLOTS) + : FlexboxComponent(window) { // Define the slots. - setSlots({SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}); + mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}; mBadgeIcons = std::map(); mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.png"; @@ -39,21 +39,12 @@ BadgesComponent::BadgesComponent(Window* window) ImageComponent mImageBroken = ImageComponent(window); mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, false); mImageComponents.insert({SLOT_BROKEN, mImageBroken}); - - // TODO: Should be dependent on the direction property. - mSize = glm::vec2{64.0f * NUM_SLOTS, 64.0f}; - - // Trigger initial layout computation. - onSizeChanged(); } void BadgesComponent::setValue(const std::string& value) { - std::vector slots = {}; - + mChildren.clear(); if (!value.empty()) { - // Interpret the value and iteratively fill slots. The value is a space separated list of - // strings. std::string temp; std::istringstream ss(value); while (std::getline(ss, temp, ' ')) { @@ -61,19 +52,17 @@ void BadgesComponent::setValue(const std::string& value) temp == SLOT_BROKEN)) LOG(LogError) << "Badge slot '" << temp << "' is invalid."; else - slots.push_back(temp); + mChildren.push_back(&mImageComponents.find(temp)->second); } } - setSlots(slots); onSizeChanged(); } std::string BadgesComponent::getValue() const { - const std::vector slots = getSlots(); std::stringstream ss; - for (auto& slot : slots) + for (auto& slot : mSlots) ss << slot << ' '; std::string r = ss.str(); r.pop_back(); @@ -92,8 +81,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, return; bool imgChanged = false; - const std::vector slots = getSlots(); - for (auto& slot : slots) { + for (auto& slot : mSlots) { if (properties & PATH && elem->has(slot)) { mBadgeIcons[slot] = elem->get(slot); mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 715714057..badaa57c7 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -40,6 +40,7 @@ public: virtual std::vector getHelpPrompts() override; private: + std::vector mSlots; std::map mBadgeIcons; std::map mImageComponents; }; diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 210b2d2f2..2daa686df 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -14,213 +14,136 @@ #include "ThemeData.h" #include "resources/TextureResource.h" -FlexboxComponent::FlexboxComponent(Window* window, unsigned int assumeChildren) +FlexboxComponent::FlexboxComponent(Window* window) : GuiComponent(window) , mDirection(DEFAULT_DIRECTION) - , mWrap(DEFAULT_WRAP) - , mJustifyContent(DEFAULT_JUSTIFY_CONTENT) , mAlign(DEFAULT_ALIGN) - , mAssumeChildren(assumeChildren) + , mItemsPerLine(DEFAULT_ITEMS_PER_LINE) + , mItemWidth(DEFAULT_ITEM_SIZE_X) { - - // Initialize contents of the flexbox. - mSlots = std::vector(); - mComponents = std::map(); - - // Initialize flexbox layout. - mVertices = std::map(); - - // TODO: Should be dependent on the direction property. - mSize = glm::vec2{64.0f * mAssumeChildren, 64.0f}; - - // TODO: Add definition for default value. - mMargin = glm::vec2{10.0f, 10.0f}; + // Initialize item margins. + mItemMargin = glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}; // Calculate flexbox layout. - updateVertices(); + computeLayout(); } -void FlexboxComponent::onSizeChanged() +// Getters/Setters for rendering options. +void FlexboxComponent::setDirection(std::string value) { - // TODO: Should be dependent on the direction property. - if (mSize.y == 0.0f) - mSize.y = mSize.x / mAssumeChildren; - else if (mSize.x == 0.0f) - mSize.x = mSize.y * mAssumeChildren; - - updateVertices(); + mDirection = value; + computeLayout(); } - -void FlexboxComponent::updateVertices() +std::string FlexboxComponent::getDirection() { return mDirection; } +void FlexboxComponent::setAlign(std::string value) { - // The maximum number of components to be displayed. - const float numSlots = mAssumeChildren; + mAlign = value; + computeLayout(); +} +std::string FlexboxComponent::getAlign() { return mAlign; } +void FlexboxComponent::setItemsPerLine(unsigned int value) +{ + mItemsPerLine = value; + computeLayout(); +} +unsigned int FlexboxComponent::getItemsPerLine() { return mItemsPerLine; } +void FlexboxComponent::setItemMargin(glm::vec2 value) +{ + mItemMargin = value; + computeLayout(); +} +glm::vec2 FlexboxComponent::getItemMargin() { return mItemMargin; } +void FlexboxComponent::setItemWidth(float value) +{ + mItemWidth = value; + computeLayout(); +} +float FlexboxComponent::getItemWidth() { return mItemWidth; } - // The available size to draw in. - const auto size = getSize(); +void FlexboxComponent::onSizeChanged() { computeLayout(); } - // Compute the number of rows and columns and the item max dimensions. - int rows; - int columns; - float itemWidth; - float itemHeight; - if (mDirection == DIRECTION_ROW) { - if (mWrap != WRAP_NOWRAP) { - // Suppose we have i rows, what would be the average area of an icon? Compute for a - // small number of rows. - std::vector areas; - for (int i = 1; i < 10; i++) { +void FlexboxComponent::computeLayout() +{ + // Start placing items in the top-left; + float anchorX = 0; + float anchorY = 0; + float anchorOriginX = 0; + float anchorOriginY = 0; - float area = size.x * size.y; + // Translation directions when placing items. + glm::vec2 directionLine = {1, 0}; + glm::vec2 directionRow = {0, 1}; - // Number of vertical gaps. - int verticalGaps = i - 1; + // Change direction. + if (mDirection == DIRECTION_COLUMN) { + directionLine = {0, 1}; + directionRow = {1, 0}; + } - // Area of vertical gaps. - area -= verticalGaps * mMargin.y * size.x; + // Set children sizes. + glm::vec2 maxItemSize = {0.0f, 0.0f}; + for (auto i : mChildren) { + auto oldSize = i->getSize(); + if (oldSize.x == 0) + oldSize.x = DEFAULT_ITEM_SIZE_X; + glm::vec2 newSize = {mItemWidth, oldSize.y * (mItemWidth / oldSize.x)}; + i->setSize(newSize); + maxItemSize = {std::max(maxItemSize.x, newSize.x), std::max(maxItemSize.y, newSize.y)}; + } - // Height per item. - float iHeight = (size.y - verticalGaps * mMargin.y) / i; + // Pre-compute layout parameters; + int n = mChildren.size(); + int nLines = std::max(1, (int)std::ceil(n / std::max(1, (int)mItemsPerLine))); + float lineWidth = + (mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); + float anchorXStart = anchorX; + float anchorYStart = anchorY; - // Width per item. (Approximation) - // TODO: this is an approximation! - // Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight = - // 0; - float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x; + // Iterate through the children. + for (int i = 0; i < n; i++) { + GuiComponent* child = mChildren[i]; + auto size = child->getSize(); - // Average area available per badge - float avgArea = iHeight * iWidth; + // Top-left anchor position. + float x = anchorX - anchorOriginX * size.x; + float y = anchorY - anchorOriginY * size.y; - // Push to the areas array. - areas.push_back(avgArea); - } + // Apply item margin. + x += mItemMargin.x * (directionLine.x >= 0.0f ? 1.0f : -1.0f); + y += mItemMargin.y * (directionLine.y >= 0.0f ? 1.0f : -1.0f); - // Determine the number of rows based on what results in the largest area per badge - // based on available space. - rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1; + // Apply alignment + if (mAlign == ITEM_ALIGN_END) { + x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0; + y += directionLine.y == 0 ? (maxItemSize.y - size.y) : 0; + } + else if (mAlign == ITEM_ALIGN_CENTER) { + x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0; + y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; + } + else if (mAlign == ITEM_ALIGN_STRETCH && mDirection == "row") { + child->setSize(child->getSize().x, maxItemSize.y); + } - // Obtain final item dimensions. - itemHeight = (size.y - (rows - 1) * mMargin.y) / rows; - itemWidth = areas[rows - 1] / itemHeight; + // Store final item position. + child->setPosition(getPosition().x + x, getPosition().y + y); - // Compute number of columns. - if (rows == 1) - columns = mAssumeChildren; - else - columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x)); + // Translate anchor. + if ((i + 1) % std::max(1, (int)mItemsPerLine) != 0) { + // Translate on same line. + anchorX += (size.x + mItemMargin.x) * directionLine.x; + anchorY += (size.y + mItemMargin.y) * directionLine.y; } else { - rows = 1; - columns = mAssumeChildren; - itemHeight = size.y; - itemWidth = size.x / (mAssumeChildren + (mAssumeChildren - 1) * mMargin.x); - } - } - else { - // TODO: Add computation for column direction. - } - - // Compute the exact positions and sizes of the components. - mVertices.clear(); - if (mDirection == DIRECTION_ROW) { - - // Start row. - int row = mWrap == WRAP_REVERSE ? rows : 1; - int item = 0; - - // Iterate through all the rows. - for (int c = 0; c < rows && item < mSlots.size(); c++) { - - // Pre-compute dimensions of all items in this row. - std::vector widths; - std::vector heights; - int itemTemp = item; - for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) { - glm::vec componentSize = mComponents.find(mSlots[itemTemp])->second.getSize(); - float aspectRatioTexture = componentSize.x / componentSize.y; - float aspectRatioItemSpace = itemWidth / itemHeight; - if (aspectRatioTexture > aspectRatioItemSpace) { - widths.push_back(itemWidth); - heights.push_back(itemWidth / aspectRatioTexture); - } - else { - widths.push_back(itemHeight * aspectRatioTexture); - heights.push_back(itemHeight); - } - itemTemp++; + // Translate to first position of next line. + if (directionRow.x == 0) { + anchorY += lineWidth * directionRow.y; + anchorX = anchorXStart; } - - // Iterate through the columns. - float xpos = 0; - for (int column = 0; column < columns && item < mSlots.size(); column++) { - - // We always go from left to right. - // Here we compute the coordinates of the items. - - // Compute final badge x position. - float x; - float totalWidth = - std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) + - (widths.size() - 1) * mMargin.x; - if (mJustifyContent == "start") { - x = xpos; - xpos += widths[column] + mMargin.x; - } - else if (mJustifyContent == "end") { - if (column == 0) - xpos += size.x - totalWidth; - x = xpos; - xpos += widths[column] + mMargin.x; - } - else if (mJustifyContent == "center") { - if (column == 0) - xpos += (size.x - totalWidth) / 2; - x = xpos; - xpos += widths[column] + mMargin.x; - } - else if (mJustifyContent == "space-between") { - float gapSize = (size.x - totalWidth) / (widths.size() - 1); - x = xpos; - xpos += widths[column] + gapSize; - } - else if (mJustifyContent == "space-around") { - float gapSize = (size.x - totalWidth) / (widths.size() - 1); - xpos += gapSize / 2; - x = xpos; - xpos += widths[column] + gapSize / 2; - } - else if (mJustifyContent == "space-evenly") { - float gapSize = (size.x - totalWidth) / (widths.size() + 1); - xpos += gapSize; - x = xpos; - } - - // Compute final badge y position. - float y = row * itemHeight; - if (mAlign == "end") { - y += itemHeight - heights[column]; - } - else if (mAlign == "center") { - y += (itemHeight - heights[column]) / 2; - } - if (mAlign == "stretch") { - heights[column] = itemHeight; - } - - LOG(LogError) << "Computed Final Item Position. Row: " << row - << ", Column: " << column << ", Item: " << item << ", pos: (" << x - << ", " << y << "), size: (" << widths[column] << ", " - << heights[column] << ")"; - - // Store the item's layout. - mVertices[mSlots[item]] = {x, y, widths[column], heights[column]}; - - // Increment item; - item++; + else { + anchorX += lineWidth * directionRow.x; + anchorY = anchorYStart; } - - // Iterate the row. - mWrap == WRAP_REVERSE ? row-- : row++; } } } @@ -230,17 +153,6 @@ void FlexboxComponent::render(const glm::mat4& parentTrans) if (!isVisible()) return; - // Render all the child components. - for (unsigned int i = 0; i < mSlots.size(); i++) { - glm::vec4 v = mVertices[mSlots[i]]; - auto c = mComponents.find(mSlots[i])->second; - glm::vec2 oldSize = c.getSize(); - c.setPosition(v.x, v.y); - c.setSize(v.z, v.w); - c.render(parentTrans); - c.setSize(oldSize); - } - renderChildren(parentTrans); } @@ -251,6 +163,10 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, { using namespace ThemeFlags; + glm::vec2 scale{getParent() ? getParent()->getSize() : + glm::vec2{static_cast(Renderer::getScreenWidth()), + static_cast(Renderer::getScreenHeight())}}; + // TODO: How to do this without explicit 'badges' property? const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); if (!elem) @@ -259,15 +175,18 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, if (properties & DIRECTION && elem->has("direction")) mDirection = elem->get("direction"); - if (elem->has("wrap")) - mWrap = elem->get("wrap"); - - if (elem->has("justifyContent")) - mJustifyContent = elem->get("justifyContent"); - if (elem->has("align")) mAlign = elem->get("align"); + if (elem->has("itemsPerLine")) + mItemsPerLine = elem->get("itemsPerLine"); + + if (elem->has("itemMargin")) + mItemMargin = elem->get("itemMargin"); + + if (elem->has("itemWidth")) + mItemWidth = elem->get("itemWidth") * scale.x; + GuiComponent::applyTheme(theme, view, element, properties); // Trigger layout computation. diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index 0ae106bd8..3be634e00 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -13,73 +13,60 @@ #include "GuiComponent.h" #include "renderers/Renderer.h" +// Definitions for the option values. #define DIRECTION_ROW "row" #define DIRECTION_COLUMN "column" -#define WRAP_WRAP "wrap" -#define WRAP_NOWRAP "nowrap" -#define WRAP_REVERSE "wrap-reverse" -#define JUSTIFY_CONTENT_START "start" -#define JUSTIFY_CONTENT_END "end" -#define JUSTIFY_CONTENT_CENTER "center" -#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between" -#define JUSTIFY_CONTENT_SPACE_AROUND "space-around" -#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly" #define ITEM_ALIGN_START "start" #define ITEM_ALIGN_END "end" #define ITEM_ALIGN_CENTER "center" #define ITEM_ALIGN_STRETCH "stretch" + +// Default values. #define DEFAULT_DIRECTION DIRECTION_ROW -#define DEFAULT_WRAP WRAP_WRAP -#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START #define DEFAULT_ALIGN ITEM_ALIGN_CENTER -#define DEFAULT_MARGIN_X = 10.0f -#define DEFAULT_MARGIN_Y = 10.0f +#define DEFAULT_ITEMS_PER_LINE 4 +#define DEFAULT_MARGIN_X 10.0f +#define DEFAULT_MARGIN_Y 10.0f +#define DEFAULT_ITEM_SIZE_X 64.0f +#define DEFAULT_ITEM_SIZE_Y 64.0f class TextureResource; class FlexboxComponent : public GuiComponent { public: - FlexboxComponent(Window* window, unsigned int assumeChildren = 0); + FlexboxComponent(Window* window); - void render(const glm::mat4& parentTrans) override; + // Getters/Setters for rendering options. + void setDirection(std::string value); + std::string getDirection(); + void setAlign(std::string value); + std::string getAlign(); + void setItemsPerLine(unsigned int value); + unsigned int getItemsPerLine(); + void setItemMargin(glm::vec2 value); + glm::vec2 getItemMargin(); + void setItemWidth(float value); + float getItemWidth(); void onSizeChanged() override; - - void setDirection(int direction); - - int getDirection(); - - void setSlots(std::vector); - - std::vector getSlots() const; - + void render(const glm::mat4& parentTrans) override; virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; - virtual std::vector getHelpPrompts() override; private: // Calculate flexbox layout. - void updateVertices(); - - // Storage for the flexbox components positions and sizes. - std::map mVertices; - - // The components of the flexbox. - std::map mComponents; - - // Named map of the components of the flexbox. - std::vector mSlots; + void computeLayout(); + // Rendering options. std::string mDirection; - std::string mWrap; - std::string mJustifyContent; std::string mAlign; - glm::vec2 mMargin; - unsigned int mAssumeChildren; + unsigned int mItemsPerLine; + glm::vec2 mItemMargin; + float mItemWidth; }; #endif // ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index dd6027f9a..15e10323d 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -237,15 +237,17 @@ based on: 'recalbox-multi' by the Recalbox community right - 0.873 0.212 - 0.1 0.3 + 0.8125 0.65 0 0 + + row - wrap - start - - start - 20 10 + start + 2 + 10 5 + .05 + + favorite completed kidgame broken :/graphics/badge_favorite.png :/graphics/badge_completed.png From f230b0de0efc6e5748228c86bb65253a1e763ce3 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Tue, 14 Sep 2021 02:44:47 +0200 Subject: [PATCH 006/128] replace png badges by svg --- es-core/src/components/BadgesComponent.cpp | 8 +- resources/graphics/badge_broken.png | Bin 34510 -> 0 bytes resources/graphics/badge_broken.svg | 202 +++++++++++++++++++++ resources/graphics/badge_completed.png | Bin 31616 -> 0 bytes resources/graphics/badge_completed.svg | 186 +++++++++++++++++++ resources/graphics/badge_favorite.png | Bin 31276 -> 0 bytes resources/graphics/badge_favorite.svg | 186 +++++++++++++++++++ resources/graphics/badge_kidgame.png | Bin 37035 -> 0 bytes resources/graphics/badge_kidgame.svg | 186 +++++++++++++++++++ themes/rbsimple-DE/theme.xml | 10 +- 10 files changed, 769 insertions(+), 9 deletions(-) delete mode 100644 resources/graphics/badge_broken.png create mode 100644 resources/graphics/badge_broken.svg delete mode 100644 resources/graphics/badge_completed.png create mode 100644 resources/graphics/badge_completed.svg delete mode 100644 resources/graphics/badge_favorite.png create mode 100644 resources/graphics/badge_favorite.svg delete mode 100644 resources/graphics/badge_kidgame.png create mode 100644 resources/graphics/badge_kidgame.svg diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 51fb09056..a7a6adc50 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -20,10 +20,10 @@ BadgesComponent::BadgesComponent(Window* window) mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}; mBadgeIcons = std::map(); - mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.png"; - mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.png"; - mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.png"; - mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.png"; + mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; + mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; + mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.svg"; + mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; // Create the child ImageComponent for every badge. mImageComponents = std::map(); diff --git a/resources/graphics/badge_broken.png b/resources/graphics/badge_broken.png deleted file mode 100644 index ae97d4c8bf31e4d7b5d717c23a080e812f1b925d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34510 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 + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + CONTENT RATED BY + BROKEN + + + ES-DE + + + + diff --git a/resources/graphics/badge_completed.png b/resources/graphics/badge_completed.png deleted file mode 100644 index 3a5c1369add0c28187a77ed6a9bb6175d0a19501..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31616 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 Rf#IXLa#`+Ds)X)?{{{F&BLx5e diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg new file mode 100644 index 000000000..fd191b4a0 --- /dev/null +++ b/resources/graphics/badge_completed.svg @@ -0,0 +1,186 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + CONTENT RATED BY + COMPLETED + + + ES-DE + + + diff --git a/resources/graphics/badge_favorite.png b/resources/graphics/badge_favorite.png deleted file mode 100644 index 3cb8be1ac9a41858717ad26f30a25835eeecd6ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31276 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 kz%#u3-<^Q7+q%)q)w0bK@%a + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + CONTENT RATED BY + FAVORITE + + + ES-DE + + diff --git a/resources/graphics/badge_kidgame.png b/resources/graphics/badge_kidgame.png deleted file mode 100644 index ccb13b9dd5205793a3e9936430ccb2baa2a7432f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37035 zcmcG0cRbha`?i)8GLnq!(ID|bl$8YVA{1<+XRPvpj2KK}EGC+#_5;vuw|%H%6cKpV;Pg ziSbuh!frjr4HrIdV@XhXvf3$*tSfiutrKme4SPP`A5tbBqC04@J{Phrdr{@CoI;YW}h}Q zS7noeyL(~aNXmzw^P4=ZKbT%SA{b}kdv?7Y;}N@W)IUAC#6{k18`(7yvu~r|mGAjo zBb-0Is+ZT&-TC!d{z8nd=!=Yx6)9HM<;Tj8p7^xYsLCb9p{*+=GCAe)k@-@s!$x{P zcE6`#)-7ii^w-kbyT;;+fBmxvQ@*{kBkSf(ns$UA;5)e{XcO&|J>LB@B?}$SIXYJy zN)N1kQPk>>2;?CfT@ z5O;QBa>c464sQHckNII*s7()Os8My{rv={=uMgqxHTIf%j#N})m^v|mWiCldm+;|g zr+tU^uI^d0l6AYtUFN6X@DaPyUR@_uyYuHQY@Mi79V|?oEX;YG&pKK0YV13tb<%U) zMk*>^s(ot8$6dblw>lfJICm+{H+_^_{=lE>^Op}^PiVaO&o{A{iMejQa_Ck0tpj=A z3)l+X2Wxt6#ypZRJSZjc{y?~3h2T3c+TP{Mx4pmSwTh8*+X>g^xw#8J=e_P{_S?jM zZTd4()y>q@8rvh?J^C$nvOt4+4=q3bk<2hzsivk@PEo<1q5}A1>CdGPmj2W!8&tpg ztEUfyNh!j0;EOtXR4gSgq!k0{oKQ8^b^uhnnkAJ-hVPa&`TX*Kn znIJAXR#m@sZ|*C5i@OXP%gV`>ef`Q#Rrm3uS5y?MLB7+PXtC3!U$f3!=qg|H;>8Pb zc~f?YCZ%utB7_6F3geWna{sK2FYa{2cy)Ai7!T)vxF_d_!Co_3UpD8 z-`M12WqH8*ReCzg%FD!zibSh2E#Kc>#lXPe9ql+hYQe(7!Y1@;q`T5={_ivm14CsH zyQH{f4cnJ*-+pHYrKXDAmA$Zh*REY!$qks7Hv#K62j$1-<>oHUKz2@!>T{jG@!Os& zR-1l%ah5twZu92V+1c5yg%zJZtzR#Eknzn>TuY0=i=3Rmu0rdy$@u8#XdRxsyu5Fm z+mG}vJUV>u*@nQAhL?LDDb*Hv%&ycsX=?hwm|bb6H_@wLbFg|Mm#lrqp{5*@GU}r? zwVxgz>5DWoFu1GNZg=uz^u*Y&Pv5?OHy@ZCDo&d#29 z%*S!cZ4qf{X)m8YKNL1ORwWQhGxN3Q=g-Hd3th8crl;3gHOueVab))IpW1VU1qDH) z({h*kb(PzPaxZj`ox1Ae^n$nVo{U{wo6;+l1jhG6ap3|Q90Shw>gv9RLqa=uPUKg2 z#W2y{jEfVia=_zk`{KrItydY!qjBQI2ARy*FJHbGm-{p6rW-GtAA79SdB?7(B+v0@ zSa`VS&p5YrhK7dJJi)V_empBLr&UL7_fAYq z7G|1?gIvv>G0rYO?>TE38irh*>oXc28+*~~F~)Fp%GjaoIQH*YeF zp3Z+^R9#h5qju@iOWvWM;Tqf$z2lBQYD%U?y6La*Y~Q~9OTpzV zyRz2SR-5K8wY3aJ9^)H|=4a03=H}|QD9T>>>r$v_r>m~3UZt68kmvQ_0e|3Dfkcf^ z*8Z{x3$;zu>o)n@704`Stkczp~ z|0M&lPP1;^%b4@-wF;BGe0D+1tZn`AxHA+fKfvrWIot+V7xEYni`uo}0kqer7>Q!uq z;}y48nb^w7$$4+?VR{zyFe&Lm*^TiPr4`ge#S2A-5%vv@jcYG$?Jr|cN@@Dz?d2tI zRzc_8bSFC6@7AqbR|+ppkM`t_H|p=r3YM{Lu^LWlRXLUKw6(u~W+%_)BMs?6VN80Z zPdSbpITFMn{jOuOuCC7bMXbnMsYi$I_zE3}>K(s5-q%FyKr@&6YVc#<#w0C`y^U!` z?P*QBWMw-VP8w}!nBbDKVvM`|gFW!FQc+Qn|BV~JKRNDdqN%3YR}geCma=!KB(U&p zO$|%Gg9RmibG*BD+Rx})w|KX1ecLgRX-dhFAY@gncO6FIGy?ilMeH8hwY z5&4IP4vkBlLBfl3=~|PbpL008t&nzDRg2Xg@tJ>+s#bJ}PZU zHY_NJuKdlLH4aYS`@d$P+B7z@Mr=N^)1~$D<;zxQ&q`EJu$XcTaXx+eG$Y0%=lOH` z^XJb$dHPftRS+pkF2%5rqgH7_9))K^p`FH6g}K%XxTsyc<0h<|ez- zkYF$F=#g@oaiQw{{rmUi>l0L7w-S;KU4IFoFm^qvovl=w_e?qvZ93LlPo4G&MISY4 zqs{)Ss;#bl&ykx8I**!ey%la(G);{XFY|3Q%^IvRd#{_@3tqoGTQ_2kdSMxBC*Z|9yQCzlm3%-JbNv9qz!Y~H-N@W&-9 zTiXhRs8{j)cpCNdW=xTD+vPK9D-LjRam^~|_t_16%cixku$a3tR}(9_N+du2%FNmL zoIlYtxebzz!tfC3-+O!lbt@!L``7yCQgwn{h_JiL=TTd5y#U`zmppo02C_o-DD{ zpF>6+%RgXG3i|AUJ$tV09VnKOJ5lyY3ZZ3=1YcEKyYW1Gy><4c0_$ZjPHivxJ9?lb z>gUi<+T=Ncf9}aSa}*3ddGf^YjPwRm+whbWQSWzu5U;E9Sh%mXX$arAabx8j*4B@f z!NE6flmZdykrHs%XUakm5T?*>qnH}|RxvSkQi&g|Vl*uZO`dUke3DP>Pj*JeFNrW| zaq;w841+(P*H$XX7+#pF>aq{p)d9IN6Fced3?4BL`l*1oj_G@x@RBysU#~XY>h#I zOU9At$u)ZFS0-PzuNiIAQx^@?vM3KTavd2NaW%4RyOX>^Z^PQ_CEn_Nzkd&pRPyM5 zerauGRW~cGa$04_=iH5aZ_P9N+Vo;H$bhQ(OeLirVUOK;{&q>9+|_yc{Ra-@jfnbd zO|GVVk2ciM@Z)tX7BwtjTd{f_i+RWIN}j7NX-4xE59_TCj&exkwLRsCVv1s-F!3F3 z=ym)g7qT(@(fm63!bYqcy7`{8T6v+u3-K=q zF=gwz=iJu$fsY?)*eFz>U@;By<3h2qvD7=xyt}nk#^Zy}n)P(^FZGO!>^{5w4V(9g z@ms+-_&XJ-d#sUIbDNJYcIIBq&u^Y7q5>aFID6)5ffoa^&oyyad_{!4KJDDv zFw}JC_YDrdcm7E&p%s;tt0N;L)%EAUzm3={QPz*Z6Rdigg8}OQmVF?7GW$#|H{hAs z->DJd+J^vdN^@iDmB!S1UrG91R*X9yop@POwN0fr6rd*p2zpS43xT;}5zH>%yUPi^14D@$BV zOl(d}Adq$Y$JVZEWn5*Q&VZ^=%n_u@4D0<^LDks&EdoMc3*!rm`|Im^ISz= z|IWU~*zjE7GZ1_t)C136hi7>HeL=UV&&18D&i z&NV(z`*E?ebk*OH(5vNGJuB}Wd6*WY5ak_lS1&I4*amkTH0L_kkZesBizdH?`RMnz z)U|g?DE#wwPiZ%bl_RY#hu{#AcC|8uOV({7k^61UX^?QHVr$9q{Mu${IoG{BJ&&x;XbJRW1= zN2A`0Sc$$0!cs}Re}B(V6Uu2f5SNOXnLwF2JKr%m`;KdGBExaQ`vnDeIW_n}fQAuY z-M#gRG_bu1Y5-iHK2Q^enM<*vW_U~WIviADE#*-&bGz_UuG-X}>V;MnTE;M(VOC}RCz?DPw zJYWh5s(utrBvNU*d)C&&-)@5nd` zoJvoAZ2j)tyP5GMAf?9pcfxM=bOeviKL&rQG4AY)*;EtECSdn2>&tKIu z+V|mQv(bSl(GsA&Qu;k!(*xn*0P(6x+Oal4%o#ypo^%S^DVImhzkL0gkd+nTeRA)U z^mN9}N8hDx{DAKH@ zxTt_2H5fGDk&G4lvB!t6KYqMfJ(#0m$O0*ZV=j*0T$gWpes*Y;e%vlgOQEx`CaN@5 zRaXE-zBk755v5Z&A$jrlaYV=}#7BE|)!Vn7o7S<~0>OK2Kll3DGP+wUFXC?RFRZ^0 ztt{3v!Au&;h4t`Ojt@Ibr@^+5>g$Vq$DGX&0S}<0v=P`*w3Cj2YUDS^}QPwKII)8-5fKQ!%{sj^f~<(po40U!8Xs+`PGcEbryZmrzV&i>iY);AKiDE5e@MUqd`{M>H|%ZW z$z#X7OG{O#ckrulvrCxUay(t1`Ed2>)n-uB(lherHJmgx{qIk5Q!1l_if)Cc70v9g zdvyfUv#&#Qucf815}TTO)xaAJF~DRgl+acBZ74ThV3;izD!E!i+(S5|*V+}1vmW0T zdH>U&&4Pk4@>xZ9Intgyc@5n&*Bc2VNwSAmXPw~&9==e(kSXyxPLSG-PM7)k_y~$1 zqSWo%9G{z-5+USWovt>TaXOxmhWs$x9jbI&KCNh;?ye(>u$s2|!HbDvQA~Ye0`e3G zGovfhNk`|me`{_otFErLlG*kbq)H2zX0!kWXvV3hE`&?2?(5gLTtEV%dZ$m{yFba3 z7YEiD5psmj#GWeo7qk_B2aZ1kGT8Ki%M|@`gbv*DI62TC(4mG!Lv$V2Zu8Mst~WbR zojSF0RtNCtU7~=@2*@ujvi^h1#oHp^r5I6Mp84vk3zoBVtGnD#FSvc@&MN&zR6q7! zdKOmJE=VCPZ`?2|Z}3F6jgxgv)Az1Sb*_fkZNJ)#osWQ=4Cc|)Oh^!PW5R}R1Jcl* zb^p#96uL?SWLS_x1O({m>Aw`Z$v9&OGR?t1Nv#LtueWl!%mpy7$s;WzV>Z(D1}Wj? zm&qxNhUwhQxaC!znHu-^$E1-umsg0=0-1m@n^o}(eZS|Xr#>^k%4D!5k9F5hs7;>W$acUQhLOWGdfaL1CdnOH0d2y)zyj9>q7VL-+1iFqoUXGWvaAi6j@qb>7FG zw{PFJ2Z#-9vAcWkUWUtvsiY~L>arS8XSIu0qCD_5>8$}HzVWwrG50^YsbkHF&Hv4g>H9T(+o zP>q^O6q8)VJ~ea2fq{W9Id(jNXHDHho4B|-ySkRk$jE?Oub3Lz8jscq1}CAg@ZPeU zg6Zfd={6OsX zMFk#F&V1P9K31>M^2gubfBwwu!~oC&YwPTfvmF1`W?SsI{ofjF|1a%0<^Twiq`ya# zse38twKx>!N(jkEu^!OqGh8T{otZHOsm$$((ieA|uz@6dql78Eo@K)ZJ^_IhUvK?OUVs{M7Z+uP!MkH>@rC`|aDQg1L$2FX<*LxLtes3P#_F4FCCKHmvsW*wLfQ zlC%SGCUbN1`!8M`2Pn(Q5s;SN;M{Uy^$H;*Iikz8&ritMe0#A5`LoF*E+S$*)}S~7 zHfYl>=B0o|BZ8iTlk=#NQRwKX!`s9WyhtuDZX4n&`Ssve$a5qr0J(6wc2<2g@*j%iQ8I z*%~8xCdAN8@o=1c0O@}ixsR+0;ZbVq9^&NW96>jy1B|>911aG;W@XFBACUa)nabk% z{j_?zPwh{-X|lfBpUr)|;iSt{rym+Yjom}w_r93WF2Z^z?|%XTH%1dd7g?Mj<12Hn z5M&=WsYoGOAwkfhwEytsyBP)n`r*TeXkr^A7ZecT3_S>WndKQly3cajuW*u1mRk@# zJM{U+=lXtlnz9P@n5TMZ96d-(Wa8oq{`~nk8Hy=z`&SA-0WC>O?b2XNz?Uz&w!Hk| z&!Fx)IAp%9{%2sSy6it@EnJ=(Z_04!dB?ri993hBsA#!2gW?ulraT>Ub3qh7pN&TD z8I`+H%?LLT6c%<6UeVVJQyF0`5&Dw*vrPg>;)0YDF5Y(T=%5?3qbrof#isl+w znaL;92HQT`*bLWRonODBaEuxEOLadpllkn~vofdSmJ+G)xpRTw{AFldLF15d4^B)J zXmjbNhbp`J!pt(E|9AlaSX9l-5(kG)>FbZowl8r1`t>Vj=Xnkpw(#puAxa_<4q8Y5 z{p;>@@uJ>$w-d(KI@zAHbIud=(ubfUo7)InO-0)A>wr$%miZj2M z*t%nn4wm5Y(`H-HG9lfn!-o$mM2&zvJEjRy%68aeWu8dZ;ggkRLnk4(J47zswsrUS z@8A6&`5Q!rg}n~mFDeja+VJdzESOeKWn+q-w;#7_)sw#MqM~ZLx@>?Metn8q8@ou) z!&QDB7`p3c{M8?E2h7!gNLt_dy?P~w+@hwhnEK{|fce>!390vhwXT0N8E9$CNN?@b z2NXnCuGltEW>CRy#8>O#9D|0hUjy)LX3!(pWNZ!6stt?WcY)&$PydW}c60A}7pb*{ zB|WPmTg>^5X0#`uK?W_)yH=a&%i-x$Ug2OVJFlo0>sCOF%**qq*j}kdV++SKy9e zbm<%!gn#=@Ok8@>VBLD{6J#UJeD^V7K0eRK9&b%`d(PfQKIw1C%W5{e`nQ`W_|m`} zE|Cr$FgGu6eCibd@NeI=@zK%J+tTMSSRj&)Lqfk3LqgU)eEis~HUFZ2K!9gX4oSuY z?LgB^7U2gI3bV*~Cm^7oV|!gaae6^DJ*)4+*|a)jTycwcESsQ}H+Q4+|K!=TQo!Q0 z*>C8pvZNJGoXU}N{PA-ULZPer!|$#@cK<>S~y)&#E zw&7-3!wrEs$IiA1q7Dk6NwX3Y@pT{rsjj`ZufaNdiildhvW0MHUWmLo7o?& zv-3wUK!m~)P1B#Yva&MP^mtv%n8~8!eA^Py-a2n%XBYb9$(F=4=jNB^Wt|5NLMx%i z`X5bHZyg(t5I$52gm~dc4STB@!Jet9zM-KEljb+wr>ow-Z=jcLExxJ*epzlzcPlLH z8cJQXGGn#>(+$8|Q$j@y3Jkm_v))#;lxiq=0LQ+ z5VGMtWi_|<3suu(1jT&I*4Re3Ywsr>0gaGykb{sj@g6!u2Y$;dDA>*XVbpsDqyZtX zH8=FmhD(%S_&ET%O6u#wO)x~#G1BK)i5H>8u~uO&|D{oK!-Pw}tMh^oB?qO2Ur1=L z%%FEq8AV!Jng9=4R#9U%EVgY;+-oFy!moR;X6Qme)XlP_SDYI=M!Kbj?agk|;J#5N%bxSKqiLOa{^oSZQ7TpZfRZyEc`}zV&YY=%|l%GEWnT(N4ifvv` zlW+dG(4(rdmX`MD(W522RW>PuQtz{)N4e!L(Lpf7TDVrlW1L(vGodDHas zMQGQmV$qW>1rHuPsEwA4x^=4quA5fwJ5pzvk9-O_8Ct*fsu;l=o|pv2l1&rR5d4ggA?sg0BV?V9Gi5ZhMaX^@&DKv=c$aA%UAjMtT zO@@*O*nd@1eZ!h7sO-ojOG36sZA>2pB!5%_E8fM;Ez7-*e&jfPY~YE*uB@9j1%-u$ z*{)Dgnx8Tw4iM`0SH_lH)Jrt4tg=RoenN?{(l z=!2xBo_xtn#f86r6hdx574=MPBQUe-!-qyB0>~-|R%+Eb9dJsd%(C|Oc$2VwUv>K! z6rBfKxL54?^XE?nJUfZcCAVyO(qyz{)v8s6idP{Rk)q@#KTnVhAViDX^C0y^<->>9 z5j+L~7LP9=FTV-%?6p71l!?##Z1ySOrNvQ9Vo;o-f=i~A+YN2t^z+R-6H|7`%H}pz zqOIaK=R5j(1;bb_&wEr=0&L>kQa40}E?Ys(D=VveQV}qioq0`2w|dp4lo|EQiR!_n z&347XXd@OhcZVuWu)%wj?mj(g8yd>3z_Fuf$~33)M@{UfpX2XiBodn{uR>->(aTbw z{E57Xgf$i;h}dQzVpm?%{we*lwzguRqaQzg3XhDu4(X0sRXE#u$Pm#``XOZUC_sCJ z9+qs#rd?imkUf*SCr@tVb{|&Wq~J;_7>A9`yc^ZomfQ<2-N7<5g{km#84eZ9>MJe$ zl^iTynCISGj>i8h=b=FfjkN*A?gm`;`BxXN!1fnV5~c^+3rRsV^z;@aGwp|?M(pri z+4}RYm!9hcUhHe^gyo4KmyW)^bDt;cAczx0OwdATfS=9z^8lVvhLv~2a)F6@-}#+- zUhyVyBl}vtO-b5sDJsGiWi(XKm;ttvo3=J%k^|mGZ0<&)-TAG64iz@v)4y;-IAKUg zMBTp32^b}tubHb3M2o$F_Q!}gCQdZuEm3~+43~{YSQhKyX(Z@PVxG8Q+WK|zhbo%>Kphc#)zlEH7kdzRJU~AcH#Uec^6-R$ zl~b!d2)lEqI`g3Ln@>{3ogE$5NO{X?aiDEXJ>A^Bg_sM6znFz~+FhOHB9gErG0l69kX9HVU)>Hv);ox-7bU9ab)9x&7h2twz=ub*wfTE@quXcSd_JcW zb}&^?8daIBC8o6}Njt+m>;xEHDR?`PY<-mhqhF(22jU_~oHhWEHtj`RgpFIDJ2zWc zbD_J!S}5$Y5ikAH#Yvhq2cPf zd;k9Z2>3U~{;mnfw~b5|FWe)*C@C{L`wg6^sd8`R^n7P#Tm^-Meo@~AjLdRp9Irkpm5sxchdd) zudO6|nDqn}mky4_qy318iJ^I}13Oa%K$B0(F@1ebG-{ky9CmbctpE72)Y$*vcVw=r z>gsNrI@qHd6VZmY&ORm6F$SayEMzIJW(i#PnxX>z@$^5*7zGyY#$(6Ud-G-T?oVPq z*O+3$h@(FO+l56(Kj!>HvJo^VYE|=+%*;%!lPCR<62<8t{*Oe5q9-OLB_+!a;6Dkus?6%jhGmauCx2|m?oMvX%8MO-=Cx{Gnn||#jOO!qYUW0k;zBH zYVnjca6AJ>Jm8!x8C6#EfIR348s7qWmChTTDkUIMxQZLzkCU(G2-hP+=;YYY&;Zkh z$uuv2$-vhvKR#S*{J6e;vIwEX!0*25i6-J|g1HE&E=bU1?UL)A)4>=L^tu=-iOI=< zC5++w1ecz|KN}nx$+MAO7ORWjQF~jYOXu~CY%4Q@H1!|`0UJLS;XyS-gWX(Q&7A48 zwKOUde3K+B)b;~$k?hmooWp>du(GkiAex6mnNQG3&am%PA)%-}inQAW-fPLICb$*_ z!IGV?MAPBRdJ(F*Q{Mj{7URAnF3Vw^w=TFd;qpl)8>R*G9x2{ z&dXoTTSEh%ctLgdFIRhdoU#%E+HGhZaAs3z_?fSJ%uXca<;9Zaa*J-0EF*FlIV>SN zdxLfMZSwJFw9=RY)>H;#_+X3=*SpXHJg!-XbVa)4y#AUjxNrzMj1quC;mrE_`i5Nx z)E<;35}8d|{)8&_1t4NAleD%No0T0>RqG17eftJ9QEKw6U0B$^K0THsHv`N8zvtp< zIO3l1@k?|H6_InSz3#=w7;a_1cwsF~O&Vy+;xzn}t=L*bZOyh_-DVs0iL}|hr zrStZ8?fL%wq1JqRc z+~ut~W#y_>7)B_m%}V^t1#Ec>J$BGIrc}D1w~D z{SVr}Z3&e^*~v-j$A^1?e5$_U z>#L-r%ecmFYPb{1P5uR*#ehLtbuByK9^fP4)T0K^ zIPKq!Dw7o{Or}F~G)YcW^q})^i{GE0LWHVhMK@oM?z)O6#IHEzFmt$}eucQElbH#l z3F1-W#={bb!X~&MItq4sG||i6lQ5%32Zx;w#|G@fKAxR+Z8f}_EU?NgT@ju0weUPs ztKzz*i`T6({rZ2#5r%&&dInz5HotxQ#zq}`50CrkNPH^iA0}pI(q^G1=|ua-@837D z03bYy(^1@}V-(zW5*eNj8+dd%ud9>*(DFsRWn^J_h){=yjMNrZ!U!Cf9Z}t*o97_p z&6^k*n!MuhPl%by-qaFo*kP0~D10O?h)F>G{(}czCSfJ;qCDZW=YVAf!lNV$`Hee) zAt4nn&o{HJ{6)wCazc#KRmC5*@u5IH5-gN_k&~&f%gVeL^JxpbyP^W0H-n0(*x01l zw)hnnE2;7&ezcMT3h>^a1PY{r>I>CrS7R{JjWALVbUm24joCiGN0*vu+m~wXqS2q=w~LN|JCdWCIa1`0TWNHjv>J`d)C!y0l~zZ=;i^fsU~RkQs1Lqxl){k za&}tQWtbP*kyBZdlamvX%3$-|yaKMstyuk{0yo@;1@k$FBG4 z(su9r+{AGV?I}=%r^(vRP6J~BmEn@DR20)qX51acR0zffdk~?vf)Jq3;9Iv?T16j6 zol!fq?kuunIu-B^|ch1g!RLpa7(O40u7I z69K|4Kj1C|sc})xdGBx=5;FE{?TfZ1t@fL2OZSX;v&X)%nmC0!CjPo8Z;K+t)6dnF z+0f@;0^K!8gUJ#C=s+N3KVy(Q7e!zgKLvtpeivGB*i>NL5dD3qy_g3Z%64lfBVC?jBQ>F#X3FvybK zI*9l(aZJ`W)Hz19mC}mmZ=2|PLIZgnaRoN%k)Y~O0I_yi>`tuY3A4C zcuPhyQluW2?>8tccSa?pQ>DTl3G6w@m*4@ZBdAbZ{n3Bo0gs8R2udCkX#J}%RP8U` zBOoWoNosLLIZR977R$l2eUbCD2}=T)64<=ikHq9ORFV(8GkcO7U^|WODJzAfz2~`( zfS6c_$v-0##Uyf}w~o)StJ!aqFfb5?b%QTEwBHl=BQwD{zmwA_G$NoI=%U(Vzd?rk z-!ylF!fzI^_T_>AJ2Nvu^rQoEcqoh-jouV?NSLtcj6VN(s#^2t(Qpjjix*N*J{|lR z6R>HEbSbeK>AZ17DRx`{&wqiHsx1AcU1Xy}fE|!5CMsEO+yUHTkcv4xCdMB-6mBZ( zt~sn}EQH}uO=kw?zsV(V9PZ5R`Y(-teM}mL0TNvPpY_ek6wqmt(s*4%J;g^Q;o3oA zZ=GZd+>^4R#}&N4lkoG?-2Vnks-p8R+cRqXeqybio3=q}hk=Ev|)anQzxFJ(*sR$<*x)q5_Sc zX6h+4mBG4XeSwMUwah+CO;vYymnZl9`t=?(Dd-sFhpArLd@bcyDFoYECr>fmTUuJ$ z@#|Lr=&P@Z5YKmPw@cG&S4ECPqx53d-Awy1MF{l1P|VG+wTg_YU{=OJKjVfC*TJkk zO>~zz8gv1a{}=7~;SpZdG3WB;#s)fI!iQ>N(OE&d0cckV$jEHOvQyXBKx z=>W#WeT2EgX7Uu6ym3-l%$)t-M9A#e)8hr@(KwwY+TAfwbwPBnh;D^rpGaPNTCqdV z+KHj|wEv<;>E;w-POPeW4K&O~4Ifp2f!G=L3}%>sKld@WBN3ni^;LMJyN3xait5aj z2i%^&2nxFhk6sp>OOYzptYKxd>RHo!p*~~9-xT=+_hXlJ;-=51lFxv7X!42zQjh91*G9& z>`mlDoS-5i#nD|?(?~+i_2aQ*WQG!@V~y=jbS3+Vzz-3nEcNs&T{X=Y#7)WgPf}Oq z<*(OzdYn}E#S}mkN@#YALAQgTWVcQS%_!BBID2^$|Y@?7-w`K@=kRjxbGRP1Nbvym+kr_zZjk ztWy&$j_nvFHb@2g=s_04V{m!=%((b7B;E%g(u;Td4}@I2^vjp5m~eA49?~fDbe>?W zC&?zgl@Q4P>*hgdNGC^`F?*k#x4hWSe!Wc zn!o?}K_NH946z7&LG!pvw>|_ngvyya2gO{LK1fZ`9%~3U1j#gHG0>s(|eta0Z`Q48+C9 z_gxzH#W(@kCsXjIq5}CjiVQm%9S>mLU7SBS6QQ`rkdL8#k1`wKcKHoK^G!<^t#itR zcbjISRlFFr+LB1Quvqz%17Mt4Ry;o=LI!f*z?Pjm38lvTaV#beLI1@;l(mJ2QSg+y zjZJPffDN(86cxoST^oKO&=m6cCDqlmn-AYLX5=@nq|xNTRtJ&M(NVUQOnP@=K_`|0 z6c(83y$R^X$ZWj{mJ1IGVmP)Hebxl6E&tGNeqP>d33y;>nl)=k%Nj_FjhZR;GbTO5 zxMVq6SsQUGQ66yB=Md6~jf6|?k~kedWgEmp5Iw4YzE*2}+v5O#6!YExusB4L?coHi zJ;D(eo>}ly{IPF6)dg(Uq||54T}AAecwz@7SsXs=?>~P^(((V!!UV%hp)S+m^e-`B z4uu%Rx!3pK3=V5%3s@SQub>;@A)taN6;6*0A!@9ZlbgRyNcl}L0tnF#xVXrVf9U}; zT$LZzq4fvP0}mT@cqA&Hnu|*=8cWbiJNo;f24V$hkkY`oFUH$@1-xO9GF4So;dXV%)WW4T z#iAuw@(Zwo9uO@C(@V6NsxUsH0r{s&V>2_!S#TRc4%mbKvl`wV@EZ_$d@^nWm+$*#kg^eli6Fn{zey3Tj}6Q^b0wHsNsNSJG}4UiPF6)$R$N!Xjk$%4F7^5)I;5~3sAT3jd;PB^TW#4TT6 zU+7!-CL3UC3a#84-YUWVfdSHTz<};@{DGcBU}bedQX%{|+OBfs>g--d#vCdzWHNrbFt#g(V+NC9Dau79if2 z-B-d?CK~ns_B10&1_{PHDBBVy^@NC zhNdoUiH^aHRNf)X2Kf`L1J@UAQqd3y+gm(6zro1(j{PO3ow zG&$1j=jk=S6urZ!0&mAiw`*5?_~=m=K(~zhJbv4LtoJDSolbHV^tr&C7-7%>UcUeI z=^E1KLDplZj(r1a0WePUUmi&1AqyBW?^u-6*|05Rk5EZ4rqOwTW)Cd0ri|urn%*se zQyNWIhn3XSrk|f2C6)$s+h$LwVFSf8zE9oZOq6H6y;e{Z@x}`9XyS^5U`Va3{+})u z^*s)=sGo33!uDSF=~IN~9mde0u%meZd{R=kly#4?^D*zgcv1QvryaQvl8;M==W4?I z6I*Rz{PRA!?DjGoRgb76`iQcwCkCo>w5R&6MC1&yTcACaFJf48@u~=J0K1qYI8lPg zWOO7&8=5le>`APL3eX7%Xu;-m4EQg^uI zm4@y0Xu$d4_EE)nm6Y1~-&JN4WdYlu|PVrnYCINSgMrAkpbqZyuU?JU{!^1?BAj-VPD!}q*MExUdT6%niI;*cJ2-~P%SXHQH>@GNQB z^FM|Y_K&?sk1|31s}ZkJ`wv@wtvy#GS-XTBq5mR_-mvTvp#1f|Yf&!WBcPzb1$)IJ zDwKGQWmWfY9zHM-Ol*oGB7uuMyxM5KlsCvgG1YJY`+9c?A25b4f{k$gfq`a(Z3vN^ zRKB0Q?4oOnC&=lq)Jyw7F!3@2RSD&%mh2K@oa%diY!NW$LaQkJ_U(7Pbi`xz-`uwn z!US=emRDA0`NwFI4v{#8o1HJ>&Mrq`YI+VI^yww&M5c8vL+F?ESA<1gih2F|i`|L@ z%WKu7JbbtkJ6BYUjd=;BeTwQ#baA-+M0`u^B6a@v9-Z!e;`W*T9mMB*^_0C}yZ^nuOYt@f;yOTT(N$ARL5e~rjjmJ^uga)V2Z#W+ zL;BRb-1P(o?AD{9SbW%e^~XQn=fK6|$636^mt$PAP-Z~a4DQM9UkghbF&Ds4#tQn2 ztvV1OZ|zkpm1S#oAt$v2;2xfsP{&;?iWH`IWuFxlaWD36rIeK7b0pKX$^J6z}laa?#%&*-Oo5^hodDr@D9VUW@VPWT2mgv_iSiz-KKBRP!e7 z&WtR}zN)n>i#$ZF9Hu;=b7}jb?e9N-Ouo21yZQaOv(~py-QIrbQ>N6hPX?OrSIHRc zYuU@B%(78SdC&32>A8A~D`GXV%Y0*ZNybn1y%Fi&^kLT@1xHK8pLc)CGhpZ78x`@O zFvWd`5Br9PGeJcRG@-()g+hQoRg;^wjvn(g2Nl7yaHg!!SZ1uj6 z`63=la>)7SM^= zxJ^VP14Zld6528fG#o+nww_kln;h%X5+b@FcWLY*1sp;q(m`CB}tg$F2lZg2LLYTJxpR zLLdc`wT$X7w)dr{r@xL~=C^lVHRWB^`1; z&Cf)15`Q3U^Lqbo@6ah|Zn!>vQ5AceeBi*Kh2%h9Hic&C;{65W*EfRn#7ya4(O>Kxo<6hys0(3z3Z%A@TTfrlPcTISCY^Mw~yt z9rpdVBsPmWpA)U*C8r4+pF+6?i1k6@&OsJfy63dC9q>~wo(k}W_@zWwBGfqB7F*5F zImq)8pUQG3i6HSH#sTGa|C-gI);X7$;n!BFRVbaT{;Rdg)}9JN#?sZX>td|`Ys7Wv53osNr^Pnf5)6v(kp_q^ z!#ebMB)*rN=$^xbLmT)SUp)fa86Fui;e$*Ar_F_le!ayJq30P9LX&;z$;tTB3D60s zoq~gc3Z@nEd?v6h=Q9w*@A#bHNfBHGr!hP~|>hTRwM>U;hT@330BE+`xN z;pXJJP}<;BS;`?26iSysk(%JPg59O$nRAGA^2HWUJ*H)9%75_Cp~Xo;>~+k~%`QDP zylG$x=|t;c;884xlJ!5s=Aj=HQN5M?rfj&8k8yyJFKoWvB*Rqo?p-O2EV?AlHmj?v zqm4xFNy_CSxg=wAFe6hLO&LwN4#v2oyxfNzL-r4HE4o)LCcO(FIaq*;cSU^hz>tf< zT&6FrCMf*R%wgI5a~hbp3DY5`$-VqX_(ovh;=OJb78c(0CfeUg2Rfw$n)PI}8+oH2 z_6v4=|6X1y@0l1J-YW!1gkoWdJM1$!eHuMLKQw&cKY5LvY7U zQt!~?gFK{k6lX^X96+(;}QFlz!9mjG-u{BO^E9{5?5_`A)x5npcTOMguOKvr4IL`$jJDq@rTRE;Xkkr3WXUhMNvA;;{;HpWDw&o2{SV@6v?V)cZAMs zNLklOWN>LeWr5`f<2xqhPMi%NK7=fOae9gTm4kBY(eX1;C=?Ydt0%Zab8|sVgdXmY zpQ$k6(4qQQ&UR#T<{Mt-qf<}L*~8$2S74k<7sv2*2}kZ{SNC_)MWd>R{;&}98Z@T$ zSa9eZtpIYGrZe0KZ=2oWFeQ!Ry;w3I?T{zbQT2(Jh1FY=7RryeqQ^{Cv&ReTh8yHp zDDv7*Z(;OR;e6qX+}|^x2-!%N#gvWj=Kdto9Mz+FVA+AxYFOy{w%J469r^dDg6l;8 z8d$JzMMh?@sU7-`o;h&r83^-8Rb18>sTiL8l-G8j4TQahEMU=?Fg9mll$Z2KphK-f0R|`l= zM!ezm{sDs~9%b={!gp#WY2#UBL65^U1xFvF9laPcE$hkRBUAt{&oblFOw&7c>K(Q^ zt(L$fvIVFuNBaT}^w2lFh5xIy?+oPnZQs_wlSrs2gu)L$!18H7$8#ocvRM;YaCSyu3qVZc z=ePFmaa`Y?vpnI#fhzIy3k!hy8P~60-~T;#zixr$$2bN&FwrA{3@O!c^f`U{G;S$f zH{V#*A6Fc?5AZLb1k$s+D|>EYa`HHYZp9r0lKAtv2_E>3Ikfw<*Li_@hvNs!j8%sG zuE=rnCt11Cd_aNmX>q}DZYQ2@eE?eN+S=NLFHu>0y{4w7zvH#|+qHNEq1HH{Wl?*Q z!`Zlh8+826;{baV!0yKb)giaKyMv1?v#Fav?!a;7vayARO6$?1M_J@S$rTA4KD>?4 zoN0Fn|7XR}B++q_e~N1?b$2B=w17ZJWv$V}hYu&aLm`O)!QoBA15O?CGcLOi99R#f z%R$=maar<>R&1PY3VENAJO~L1A@jmo%F4+xHX?UOcLF8T=PzG~1%$DwKeommraRdB zC>Z{%C7dd|-lBl?1H}x|M88VYeOInr0c+52sYwy>0q~nPI3%pQUi`VIh7T<*tdQ+b ziotP(k&q*d$gzGZ6lrZST6lc7BP&iiIZ2>0|NCp_HINXHKA%N%Mm8&t;D5&+FkOM1 z{{12I>Ie(s`f*V2z45x+Aw@Jvc*v%8an!Wr=$0FN5FPzlaMB-X)hG7oGkciO70cz8 z!;)!;Ml?1wHxB@-*V}Cw?M+-5j#oKwx#tsil6RqW4X_vf7&xnRKc)-JzR|cZOobLM znl`@J(nUo+B*6;ac{XqTfo(%b@ps`kN+4Ha7>LAexj|6nGNG$mXdU!lQHDGZrdxjx zVkWpTGISKY27boRuiQE6-Q!K3yBxrVb@)r=0An0H^x*yTHwcmYSy12!AH2+JckSB! zwIMjB5LMo`v^SNgeQTZ0~>?*#t~awJRIWi!kRNK8W|f`?2!BO2{kYlL^vm+ zW6>ggpp6K-AK0UX-N=a_x<*l1*?LA04s|~G`1y34#bo{X?IJhDxMD6Y4D>Xg9|kz* zJsQaclUJxxXx&j8Cr=Bz0x_3u(7J;{hC_-Tod%qRcNPCUs%QjDlpxTIo(W%l|2Hym z$RJP{Sy_ECW+I<28VvZ4jti>@{)yp7N0_#4TaVO^CHT)@aW{I06$GGZavUVbP0p`F z5JLb#^nK%({&l2ZwnDQ-ge zAilRnMY>i)V=dWSF0-RSz^JLHsNNt#qQ{F;=@@cZ5Xix%_CuN41KJhwC`PYvo`28o z`s--*AW8767(j9e(uhs1@bdC9h!(sOf2@u)HTMOuePmun5B4J)>Z=p`@``^gn6(w8 zTTTx)=0I~!V`ZbP9Bc`$ppt$W+&^~6xM?@B0<*{gY9!_q1Lo|O3!@;Nm>@soIXHy6 z#Ou@K%#4TS3c5Gn(XoqMPJ$LO#VY3T^((n)766r}n-UQy&deIYmE?@W6B+lS?rR|P z&o*>HLV1iUpF_;@QEPA&2;mAlXd!CLfm%ZcTns+gk3GD9e|MCq#X~+B8GWq2#DWpp zb?a1|qnJws1{d!H2RD5vEdrZOZ3t=Kjoh@MuyZo1b1q};uedA%Knec@XmWR3o>`bB zIwZnau|e)vQR0i3yP~ST4B<%VGGwheoSsXnFvya492FIHX2@-#aw~O&fvV~*3#e7S z^{ddLql!TC_^l??E1N8h z{sq>}mNE@=CXYkmHuIJ(A3I^3`S{f<=deN>;L<#qvv}$}0$RI6O)ndCAe&Bc_!=di zsnaCtxw3SHmg^i3AK!x@4r$#hXyJqz$#Exu3#B*U`V9cX4bW7|O_ZJ-(dlh|THQAe z@@iy$@E{#mob{neV#12|B`7X#Q-AG=`;1Q7#n&u&>O8AK&!6ul8sN!K0;#?EbUP%O zq0uy*v~X^K*}+GPcs4;;fWVqKi{T42aOYM z#2@GWUPYpK*2WIF@*1o!!dEatQVAqaRl)$+6;Uphn*?n;MDzar3-^N|JESyi7vKj% z%wqx)gX8GAz8?8aw}ReFvXwJ(WMXDm{aXOdfD)JK^FtPA&Ypc2efWAqK{1LiE^coB z*lTQaf^6tB&^}v(a)3C}MKQ0iF)5IYY1_7s6&AIq?nAHvQB=vPw;c>UMZ9j;)#Rch zaN*T8`5t@dWTr+t^ahF$$H@JbJIgE%A5vFW7Yr2Hj~bM$8*_tEfSc6ptc{@g&=LzP zzDz5aMwnuc={|(X$8FLAjkRVQhvUf$H-!^QXpH#~0iLSo(9lrocy&OWf(IQ}WCSt< zvu}xatY5=P$_s~-&o>BLvcS48H$mG|VZa5ZYP+1K`*DDt@n(GM&jxZGQIntPTf{RW z*JnT6!icnC4)Qi{=pPS6gQ&s+E%b}}US$|%p?LB79St23p?spQLCud^Z2~vV?e0?b z?j6-C9F!V0Q*j*8>P!Bq;LzFUT~<~2-UZh-QKvTap4D3m_Iu*LBh@#9Gk_N9$&s;X zK&8*y_`^O%UI@d|58D13Jpb7kY#}U(7KMy$NHn?c)Ie0L=vuhRzL6!XW zA>Jqd#RcGMD2!#FR3|`j&&Wu6_}C*H^;HFW{jNVhZhk`yers}x;X|prUe6T4LXkP1 zUwUq3nKs3;1Q$Xd6ux!tg?BU74B`2NTi=cIJyxwN!g=_boNWgP$2s`-auZPZ&KM1? z;^980w?@o}pBh-B!IsrT926Q`Z)${dDk>_Lo9Mr^eWtpouS3}c6^Ls3m?-&~?=S7* zTiLT-Op^6c-aCW3ED}U47k3dTws6C1kES5D7h>_JBgLK{pk8w6jK(@(v== z)H|=+*{SMz>xevEGXz0RV`*%-sG-J`sz#0 z7#McC0%5xhKQ6|lHmJ)UqYc%)@W+JDgd{W@R>eT#8n^jWU40qpE!*KH`l2C?Jh~_kcJ`dsLd4u=-XVpv;~Jba(tg}*giKA>v)d1eg~ick6q98)@5kb zqgUCWnM*Xu%P;IhCGblA+c<^X{HRak|GT-vpKN zhPKbJkyEz)4kq6YIN!?6mNHyqU614iOt|hNhpv2Ph%SZqX}O{y+L;@Y0K3rgyMkQw z(sQ-6`v`Q}d%=pM>QkMr&*DM(<^`M-OL@QO_OtKbJKxffAzRx42P%8>*SR@4L=#c&jJo!L?q=G!+~;VSj|xng%lj1`0tj1uhp~ zw|`fI1YG?XqpeA(r}r1YxC4m}@X-c@!4LH&2fF(e5oqU^H2p67yZbQt0H3%aQy*yK0joS^daSR*AcxS_e0z2MYD}B8NujK|bLNHDH|T*$ zpO=t6e0V&T z0ZWh#om;L!DfEZ%rWy}p#pcDlTX7FZ)2_eePHjd!XUjYC>?A*So!wZcGVWPQ&}%9O5o-KEga{Y?^r*@ zCp}SLzFFil{lc)%c-s9j%i_WJX ztbP&+e~ZBCPm$8D1r>tz>3ZVmWXHi7D68W<5Z+)8X`aE^Tm9DGxD^w=%)j%JY)(H@ zC67NF)T)fyIw2vUK`OmeK^41rNzU>zW=(Wn7|VkxQh80yXm-~f6IPpxhSt`>5&ABt zZJ&VG9v-p3;k%BVU3RhRBKrDUYLaU9xA#wkoiM3-%&$6?pcn(|y7Kq6=gyovSB0Jh zv+_A*Wd*>&)UHlQ3=8_Z*1Bv}c*k^Gec(=ZHl_VxDXE{}dZsw)f)^Y+XkGg9<)c{t zptRrU@eGyN+g!amtA6QFla2#4LyVTO;jyuq1%lVWJebeso!X-h!%^iO+GMkL*CP#$Z}7now7QN=1t!6$I3i?n^FyxSQ7)+SbTt;caxhSPUY}Ele$wQ3( zB$9=ZahKEN0>&fsS7r+o2^!=07aX!4mero!`|8F>Yf{C!aV=M0BrhMU_-m5{>uZCv z1~Y6$@@uAjLeWh@H^SL-+k$%Ed&EMumW|b&og7dVpNW{jN3N4$n%`rx*#Mdw zR4e9Dac81+k;sz*&m`s{nff_4)lH*}r%DMcpK zk2uOBJ*H~oYb2gLF5hwwz4hmKA$94f<7jk6O(VxhLA1errOas!YV=_#W)F63F7o)CXVJV7 zUBfw4Em0z3;b-?*sJMoq5)|#+OD)CgI>i#iqoshpM)=0lvh?wmo?^Y` z-un#RA_@~431(0z2=rn1$YPAincw`{Sx;pKTXZP;155Q@s(YTV?$AD_Uu1!w==qYnF>ukbY!7;PwcF_ zOwwiStieXEBqX*k3K<_UJbPBm&a?#o*#LBCj*6axvBvK?Z-NIc&;vXQMZXZySiQiuOrB#+5}ClIan`Jxw0GNnTB>c#Bmo1o(H>K(0l}kgec><3>IKU}7lCmNM z7-<~NFh(#SrSViu!_*_R0khDwm~Aw~vw$Z7&|Bgx1;|WYlBN;n)8p7!+#@&il1m_Q zU<8LkWh@UAeBLEtBFn=9Pp)YZb-y4o_4`b7Mj$YfW1YNXVX{WBfFcuV3~Swc!?oCU zC_tfPJKQ2vE_nap!$ike`Z!uH$d+|2E~XE7#~m2DZfkqbFBHe#52WqWH!_!Dx3&|^ z6J-1h!yo(}_=N(|TY{a_97r_F4Q>Pyg23N+T#M}W@Ef(@()hxq-Vn5;4Sho5;wKTo z2}SZibX8>~HRujr240A=nU1>XS8Q1!ayv=K(Is`q)dmR%`eKKHYX5s!2*kS%!14Wx zom;?PfFg$%y`z=b{?Z?AfL}k2|ZoyZ0aAK@B_3^8)5(z$OtR#C9g-lq#NbHI*n?MaO)5KpCc121&=_= zJ9@!=BUiP|XN{7^+9C%FpgN6<@!VA0ue-YLwQUE`e5TK=T4yS3?Hn?sag?{sQZ{&` z&hIl41^=M{-$=gXH*r--%Vj${96qhm9&Lma)>rD35=5L-NnJ$_iu()obWRz2inwqn zJP_4J1W_%bLu;a3wx8L39gpkplpJe~qo*|1AEl>Pz!puOT_1xzmYW%>@IpY{8g_uB z9B9(%(tRVIgrv26;P|;HSO7gmv-L}X6;eI=LH=lF7_@AT&PaB=WR;qh#vGkd-DtyP z$ErH>Jf+(98%$QbjJgLKQ>Rx-6B6Xcn@*qbEwTnO1C#0#WqZEHNNJ57yp0^HWZ=B^ z)(Zc#`%)ddrBTq>4>bkJtA*yQX}FE1VXkbU68r0o=0%X(ty9*TdKXr3Kx#fMIk~&0 zu}?5A%^^o+XkN0qXt?CYNMyw!A$eJ93Y5g^9>dx?KNG>$-j^?+!dx)8cpr|Ly%7m) z@F0O!?}7|NPkhU`szY__+4V6h+^>V~gLQ~nxI7lJTv~oEVd48yDR){6tWu|{PXWPF zwtk&677A?Y`r4uEtcN|8UUp9bE`2^bxU{@Xon%n#q}r1Q_JGefK!h6BEi7BNCQUhr znVhAOAJM{apAaDINgkQQ18dB+SwWS^a4cpMGgsq$CZ>D5N(*`<$gD!)>_3-l(ac{y zeMnoZ1D@ZzC6Gi6O?OT zK?H5+xpW@^Z{75K40DVqybyV|XZkj5Rk&G}aeQJX;+Mr(f4{a(tYip>-V3Vb1<4W@ zbrqF+XA1RFJm8u71}?||9#MchOtQL+zUreoXKo_AZSk4VZNa!<{dN#`w>-J!;_Tcr z=DF$`AK?jJOu56RZNhR{%_q?f#e5Czf#vu6{X}iJpYcB|!#DsJn_Vhg8+YDny9OGZ zMoS8dXiR>-LrK{f3>do?4Rk&vt7&UaXA?CJmEx_@^9wAogz;5V{&G z;`~D-g}@L%)QNH5a7yb=#@Z%C4#Eb)b5fHC0e2fjDsN99Oe6#@@qK`#9Jv(}-9N~Y z7%!{>KccQm7ZegIN7he9c3>?mWh*!V;U!8@AbmSu2BS!a?acG<=1#Mt7t=1hVnT3i zdM%!pbBIMA9b4v&_RY-LhCbcl66Qz5#S2|LGlxUg z-%|v^g&9F>vV+Wb?+hZQ&Nuf-NZ8}EII0RMtLeCt&@h7Epw}mubyuR!q^jD1 zPEaM*LJ)BSCO->W4icehQ)$b@cQc_va zv4fmKbxs_1HS{j(>N>Un8PuF!`?HM!VJ8M?^7BvJK}P(p`B|l69Yr9()%4kakwf{0 z$SjCH^Fj1g88#25^M7AH(TLVfxvJqg z*z-NjwK6e!_xUrOw98a?@cDS)BoLmQO8t3Hl#usOrpDWUW~H7vMY3@aBYF z*6lBE0WpEWb@Gxp4#BriWxc7swzDlL5R7TieS6+3%q-G*&U>Um1oz?<|LMLlD%-8m zD`1BnnOPkymRFmtOddYu8xxx{dtDmB6A-Og8gKN$t`OLan#9fn19q%yR*J;8vatm~ z=_R(113bEFXXq(g6zeprBQPvrU}0(McYWbNxWQ=V7?pcbvz?nP_IbJujzbNt%Qix4 zGyaRn7db7m(nba2s=I7!ixUyr-+{Crf<8ga$iKs6}(qAf^J=yk^c(FEfG`+N(hFVnvYX!cCzI?*fbktmUiF@J4C zl6b+bQS>CDo*&vJ@BRq}-CH0(Fq?Yp8iU3l$y?{1yXPqCxG_Q@KhiU^Z;uYbPrKYjbu*y9|k%gH#qK>Q%HIP*;F z{sBD(PFH@Q%fNcU(9Ddq6v3{43jls#5~hx-y1D^~%AkZm($0+aLI_G3`XvFt=XM`G zTBhT=x)(jfx_iL*$x>-|HP3_o98p|tOg{olKrjvKvM-&?v+3dhL%|1}Is}Dp33j4v z80ghQ`fiZ-qY4d$ha3=uXaF-HgSyOjVmy&gNJvPs!3ntXyDcT3T)n8P%fmUiQs;@` zc-_dn;?hyzWg*LYrbmQ)85uXw(Y?c7c{ST$3N#{a6Bh=-AsWe|QSUO$pH#q;~qR=^eQ+mhd7OQuL0N7r?~hC z=m4KQYhsUvAoCJ+>OOn%*XU@?^6Jsv-d^~1$)oA#)&j}?cKjTM@hEa!0WV1IeO(6qq1JZd5S8?}^1i<%nQDRIx+x9?ZB z8KJ-tH3le|=nopB>HYG?2;1GnwcsJt;`N|Uk=^<9KK>bL4>8Mjof$T33cZCZi2Q(d zH0m2xNqg>v*)ajm$2yrC&#I|mULyoZOsL!Nzi3EAAOja=Pc((iY3u670WIzCERjMC zrTpW}PN=_NSI{G39_l}2>b{W&qzbgc%Tao(YH0W%4>tl9PAoI6JBw4AGh^ZNXo@v)5G(#1eS@aAi|+t?oM`fU0>YQK z-0pICONGL!07p8gDT57GSHUa7Y05tbKMFe-c@eJ(>;)P<+O=!f7XPl1dg(N=n{>d? zQ5&Z>Hbjj{tlux2nDn6TzJ+CcH?9wLAlfygj7AS6@ft`}qq)9C zZi}X-Cc&0MV`73}7SccRooPKMC-KA}9^$}g${QXycF@a01w%XxiEA8M2l%KjA2H1q zO}lp(`3cJK2QTh!5QhPz61!vU+N-j3B9Dj-F$u|p&*R1FPh?@J_fTwHVcliE5eFM- z)zqeH39y`yN0Lcq=KNvxQexrypw7KJb4S=*0&5jwf?a%kfnY4efN|u5PpE)`s?u~P z$j4+wpvPb$M2F;(0Wgaph^EG@>p&JGe&a|L2vyGyY33!okF4C>OG0cP0Tu(N_EYB} zzXsTfG#IlPXcFMMjNzaB;8RG}lZ-gOUjDTV&3&9FT#$)Y3aS7``!&Kwc?gV0)uTF< zznT*^LlEnQ6Dp>aQ~^~qWvT2@6*#xiM#3LMD`GEfT{YNFoeP5mj0os%`}$rpl%c$A zWfcUoTqA4@KHpn{Vq)*n>5+H!0gW@S20L*^8Y8gb#t&nHUgsr8G&4)c3Wza`S6T5%}I^aF+bZFO~k*87z{bV^## zl&Jo#r>*^_wRIt7_7)1}?HD=$w->ygA@wS6xW3({oH6-0I-pK>rMx)$*B)L56n z)r^2fpF~&_fCV4GDZDDxV;($v3@!^qPy$@nV)+gcfYyz2iW%-r$0Cl>Y>o{J`^NX_ zAP%=!8TUL74P)>gAO-lzp8ojX-d++CSm7a#)qRH)0x#Vc3zRLRGN`!RA7VQZz!BL# z>H2FH%76AA5DL7fi3_z@7Vd+rBWlF%2lkI4l#{xv$6c;Haxv$?vL}1K4wnq9WANRC ze54&J1JX~&>89J}i$HXzz@u2{gsur@L`>UhNZuIshnt5XAYu^xvsxoo(VsPh9fGcV z(Ck=OUzCzJ79|1!flAY5GCY31ryr%G=RsbNBnY>jFyZj&vM`ymicK9P9R+=^hQXlVXUKbaliiG+U&iWm!@_Dvi%=xfldE{}N%K_Y-m0hfaFBD|6ql8ZH$_8dKsNnZ+(CaELang{i==0}yD1XT8 zy7{l5A2>bz!z(PtTxeaLS@fauJC#4GpaEdm{pJ=SB0~TpQZx#j3kDfyht5q12Ab;N zRIe-Z^i;JR0zzm&YKQ%WBgllSAZM%vH?Yowm70obC)^^zY508U8&Zkh@;$Ikf#0&6 zrX~xtWXX-1vkk?h;Dgt%j>WPv#KM{1IdsJBQNfdR0Mx%9KYr8}+8CrCVbq3?Hk#mD zpqOw`)dFW%5(w$~&HD_Ilj?frD0Gm67wkp*k=E%HGf0I=6BNUYgTU_TK~*jR?+{$X zd?G^%nz2NrHiwz7&+<<;eMQEP=YIX_ksfpdkp{}euQY-C1#%L2*0;R9y_*aab#>W% z>9)mbN%I~Gyr)ct0TG^r_uzpBP(wfhBowmM3_$k)xrurOfXrxwJBb(Tdv4Gv^!R?EB6T` zSzlm64>@M25w(A=)6Dm)^AedHKs_r6r2w6oLCdzIc%1B@y0OBM4@4~c0{6RO5)u+m z4az(wh1`%!l9>sX?JpUD(i3j@eI_)vq3r+G_VWtzkw;Ltp*8$6clrauY8E&Q1Q3O4 z;fY7Aznx#*w?>Y-mDGOlg!U1I#`4ZGOLqux%aAhG%_hNSb+GEdiy^i z=B~v30|bljJKqyBaZDaQ+kUX&w2f|ncm$3wA}`m<)USlet{1v}S==tlm|cLiNeV2m z;)&BEi~Oef#YJMI`yLeoNlnnq{drfQ^cd77Q#q6hxOp9-%q1hu(c<#x<{=zs^y${4 zk43D?z^MEZ*N0X}g@Nr*goaLYptiD0^Y&X&>dNxW-X~;Vesvub+ z9|ZJT@VhRAKSVx!{l~tiuwjD8NjB- zKqOzfO!M6ReVa&AfwK~Ge<8W|phAGFPh{^NOA{w?u7F}ii-}`G3o|3B*F#vsW$-MMzN~jGC3?K?H1gJm;KS}U^Ud5)}LR=|M?r5S!no#-D?&mms zqEJ7w6FxKp48bZG0s{lb;g;~HzuxXKbLhAWG)*+f4M{?l1;~WrCfxerlTeFYg;la1 zPtgknPkH0M|BSCdCOD^1w;5Tg)9)|h699BzJWq`jw+iGx9ad@>7~jPioGg+F6dqjU z-qo@0ZVI4w^AV*{QS3-rdjkgxmUjO=XsfoUdha!fwi{{E%J zQS6ckfF1mNVwN#&Ox65LMO)h+dS%hJNdQ@OxmV92Hz4`${U(~zR5yuJB7$JtU^>Y{ zfRqY>C&hC7GAgyZ;I&-Bb_iMSL_mWx)a1?cThEiglc>aA*A-uwlLHO(=5b_9zm=Z) z{U_I>rmp>^Nm7ac(cs3$EhTi0k5>#OD zunmNX5xJIgN{-jAd4bn1Y2SYnzfWeS;20tz280=I{hv42P_imaJpy;1bg+Q1bOV#( z^M@_{gKl_GR3ML@h+SPe$=fy#sWLrUsd3vr`688;Cz1WeaEn@m3yM@?LV)h?6LCw6GtFVQr!$Zxf}Zu)6DqLg4w@`8_EnPFyw>` z-%!M`NhP9`pxLHCVm~U<6o<#yiNpW} zMlPwV`%O`hbz{(ny-I5JfTeY!fjB2ZKY` zy#wpdjZ(}ToHl~xqUD2xer@dw4c>I?bA*xq`Ljf1CJXG%=@GSc^*o{U$9}$tax8t* zU1_5VD&p-2jI-{;0$Feb;6k#Z>-q(*2q80KxnxD)u+{Z^*VNj|hAl~a7aRjvfLYKW z@o5;GQ!$pD=(b|355QpX3uPV`Q`-nn0^6g!xmoMdP{R!hDIDbpuvOxQhroN?jAq}{ z=@AUL{kNu85Q+$5xr(8lq}iPBQC|gt#?!<^h5ZZ1A$@xXoNpW;3a7iqo2Ox>y5`~!I9LKtwKZcWL~yIMx(XB|HL>h!1Np?xbKM}Ih|YHB`gjpew+ zTBl0Nvi$tU)aHbbBA&|&4rBp<`jnzvJOlm<;Z~3{7|-P<$kN@|p#(ueiscWa>G`Lu zf&vB947h*rz=`=H{BO*rOYLIFxE`p4)@JyEcy(f^ZRf+9Ig#n_iL_e0A|?=o#I@?< zaC|{L=RtLo1;1a}3PbJ~09|q{c0d?I5CBY&tMah9a)kz|FKYWY=nw24x}dB3BLB%w z`f#3}{8t0V*X{d)ci3?0ryaQG2QbXp8+rOqYK4BU z^TUsq+ZkyRlatqZF1B^aO=}Chp-D@i#QZ-3&knr%wsyf3z?XKJhcwYfx{Tt0#fC_( zvCsM-csvf1c!+Qgn=PBwoJ_5N*Os?Wtrs|wxFyrMk>*TJ9N|3GBUh1E0K8m>pC(!0 zO#x9iMyT#nNlAUz=#9~BI^vB*LdW|3#R2B;zhgJc=;8qE1xPgmsr{3WV<1KmV@X{#q#00H)q7 z{1^ODC6aUq&J=tB!L>IW+rcMkU+=s<5;qykB) zv&YqDETDk^=SW3YcSCpP=xSZp8qqAPeK{hYi}}t z77M$Q2fz DyG>Rt diff --git a/resources/graphics/badge_kidgame.svg b/resources/graphics/badge_kidgame.svg new file mode 100644 index 000000000..e5103f881 --- /dev/null +++ b/resources/graphics/badge_kidgame.svg @@ -0,0 +1,186 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + CONTENT RATED BY + EVERYONE + + + ES-DE + + diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 15e10323d..a0fc8861a 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -245,14 +245,14 @@ based on: 'recalbox-multi' by the Recalbox community start 2 10 5 - .05 + .035 favorite completed kidgame broken - :/graphics/badge_favorite.png - :/graphics/badge_completed.png - :/graphics/badge_kidgame.png - :/graphics/badge_broken.png + :/graphics/badge_favorite.svg + :/graphics/badge_completed.svg + :/graphics/badge_kidgame.svg + :/graphics/badge_broken.svg From b3ea0372212b206b14ff5355d891e2429685a3e8 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 19 Sep 2021 21:29:35 +0200 Subject: [PATCH 007/128] object to path for svg text --- resources/graphics/badge_broken.svg | 458 +++++++++++++++---------- resources/graphics/badge_completed.svg | 201 ++++++++--- resources/graphics/badge_favorite.svg | 431 ++++++++++++++--------- resources/graphics/badge_kidgame.svg | 199 ++++++++--- 4 files changed, 834 insertions(+), 455 deletions(-) diff --git a/resources/graphics/badge_broken.svg b/resources/graphics/badge_broken.svg index 3da5efcc3..874679512 100644 --- a/resources/graphics/badge_broken.svg +++ b/resources/graphics/badge_broken.svg @@ -14,189 +14,279 @@ viewBox="0 0 210 294.5" version="1.1" id="svg8" - inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" - sodipodi:docname="broken.svg"> - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - CONTENT RATED BY - BROKEN - - - ES-DE - - + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + sodipodi:docname="badge_broken.svg"> + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg index fd191b4a0..0caec97b3 100644 --- a/resources/graphics/badge_completed.svg +++ b/resources/graphics/badge_completed.svg @@ -13,19 +13,10 @@ viewBox="0 0 210 294.5" version="1.1" id="svg8" - inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" - sodipodi:docname="completed.svg"> + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + sodipodi:docname="badge_completed.svg"> - - + id="defs2"/> @@ -135,52 +126,160 @@ id="layer6" inkscape:label="content-rated-by" style="display:inline"> - CONTENT RATED BY - + + + + + + + + + + + + + + + + COMPLETED + style="font-size:43.3061px;line-height:1.25;font-family:'Bebas Neue';-inkscape-font-specification:'Bebas Neue';stroke-width:0.295269"> + + + + + + + + + + - ES-DE + style="font-size:55.3975px;line-height:1.25;font-family:'Bebas Neue';-inkscape-font-specification:'Bebas Neue';letter-spacing:0px;stroke-width:0.364455"> + + + + + + + diff --git a/resources/graphics/badge_favorite.svg b/resources/graphics/badge_favorite.svg index d6c8a35d6..775402871 100644 --- a/resources/graphics/badge_favorite.svg +++ b/resources/graphics/badge_favorite.svg @@ -13,174 +13,269 @@ viewBox="0 0 210 294.5" version="1.1" id="svg8" - inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" - sodipodi:docname="favorite.svg"> - - - - - - - - image/svg+xml - - - - - - - - - - + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + sodipodi:docname="badge_favorite.svg"> + + + + + + image/svg+xml + + + + + + + + + + - - - - - - - - - - - - CONTENT RATED BY - FAVORITE - - - ES-DE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_kidgame.svg b/resources/graphics/badge_kidgame.svg index e5103f881..25b9a92db 100644 --- a/resources/graphics/badge_kidgame.svg +++ b/resources/graphics/badge_kidgame.svg @@ -13,19 +13,10 @@ viewBox="0 0 210 294.5" version="1.1" id="svg8" - inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" - sodipodi:docname="kidgame.svg"> + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + sodipodi:docname="badge_kidgame.svg"> - - + id="defs2"/> @@ -139,48 +130,152 @@ id="layer6" inkscape:label="content-rated-by" style="display:inline"> - CONTENT RATED BY - + + + + + + + + + + + + + + + + EVERYONE + style="font-size:45.7811px;line-height:1.25;font-family:'Bebas Neue';-inkscape-font-specification:'Bebas Neue';stroke-width:0.312144"> + + + + + + + + + - ES-DE + style="font-size:55.2569px;line-height:1.25;font-family:'Bebas Neue';-inkscape-font-specification:'Bebas Neue';letter-spacing:0px;stroke-width:0.363531"> + + + + + + + + From 4959e72b359b72a48efe4d4d6e01b617620885ae Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 19 Sep 2021 23:21:38 +0200 Subject: [PATCH 008/128] fix when badges are shown (reverse) --- es-app/src/views/gamelist/VideoGameListView.cpp | 8 ++++---- es-core/src/components/FlexboxComponent.h | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 2d2f4faa6..05c2ff62b 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -450,10 +450,10 @@ void VideoGameListView::updateInfoPanel() // Generate badges slots value based on the game metadata. std::stringstream ss; - ss << (file->metadata.get("favorite").compare("true") ? "favorite " : ""); - ss << (file->metadata.get("completed").compare("true") ? "completed " : ""); - ss << (file->metadata.get("kidgame").compare("true") ? "kidgame " : ""); - ss << (file->metadata.get("broken").compare("true") ? "broken " : ""); + ss << (file->metadata.get("favorite").compare("true") ? "" : "favorite "); + ss << (file->metadata.get("completed").compare("true") ? "" : "completed "); + ss << (file->metadata.get("kidgame").compare("true") ? "" : "kidgame "); + ss << (file->metadata.get("broken").compare("true") ? "" : "broken "); std::string slots = ss.str(); if (!slots.empty()) slots.pop_back(); diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index 3be634e00..45c82637c 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -28,7 +28,6 @@ #define DEFAULT_MARGIN_X 10.0f #define DEFAULT_MARGIN_Y 10.0f #define DEFAULT_ITEM_SIZE_X 64.0f -#define DEFAULT_ITEM_SIZE_Y 64.0f class TextureResource; From af50921d4bf116c21b1b41690573f14695d52c5f Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Fri, 24 Sep 2021 00:05:32 +0200 Subject: [PATCH 009/128] merge master --- CHANGELOG.md | 54 ++- CMakeLists.txt | 226 +++++------ CREDITS.md | 2 - INSTALL-DEV.md | 59 ++- USERGUIDE-DEV.md | 259 +++++++++--- USERGUIDE.md | 44 ++- es-app/CMakeLists.txt | 374 +++++++++--------- es-app/src/CollectionSystemsManager.cpp | 41 +- es-app/src/FileData.cpp | 26 +- es-app/src/FileFilterIndex.cpp | 26 +- es-app/src/FileFilterIndex.h | 27 +- es-app/src/FileSorts.cpp | 3 +- es-app/src/FileSorts.h | 54 ++- .../src/guis/GuiCollectionSystemsOptions.cpp | 17 +- es-app/src/guis/GuiGameScraper.cpp | 11 +- es-app/src/guis/GuiGamelistFilter.cpp | 15 +- es-app/src/guis/GuiGamelistOptions.cpp | 12 +- es-app/src/guis/GuiLaunchScreen.cpp | 12 +- es-app/src/guis/GuiLaunchScreen.h | 13 +- es-app/src/guis/GuiMenu.cpp | 25 +- es-app/src/guis/GuiMetaDataEd.cpp | 47 +-- es-app/src/guis/GuiOfflineGenerator.cpp | 8 +- es-app/src/guis/GuiScraperMulti.cpp | 12 +- es-app/src/guis/GuiScraperSearch.cpp | 43 +- es-app/src/guis/GuiScraperSearch.h | 30 +- es-app/src/guis/GuiSettings.cpp | 31 +- es-app/src/scrapers/GamesDBJSONScraper.cpp | 13 +- .../views/gamelist/DetailedGameListView.cpp | 32 +- .../src/views/gamelist/GridGameListView.cpp | 31 +- es-core/src/GuiComponent.cpp | 21 +- es-core/src/GuiComponent.h | 9 +- es-core/src/HttpReq.h | 2 +- es-core/src/components/ButtonComponent.cpp | 65 +-- es-core/src/components/ButtonComponent.h | 29 +- es-core/src/components/ComponentGrid.cpp | 24 +- es-core/src/components/ComponentGrid.h | 55 +-- es-core/src/components/ComponentList.cpp | 7 +- .../src/components/DateTimeEditComponent.cpp | 15 +- es-core/src/components/FlexboxComponent.cpp | 33 +- es-core/src/components/FlexboxComponent.h | 1 + es-core/src/components/HelpComponent.cpp | 34 +- es-core/src/components/ImageComponent.cpp | 22 +- es-core/src/guis/GuiInputConfig.cpp | 2 +- es-core/src/guis/GuiMsgBox.cpp | 24 +- es-core/src/guis/GuiTextEditKeyboardPopup.cpp | 346 +++++++--------- es-core/src/guis/GuiTextEditKeyboardPopup.h | 64 +-- es-core/src/guis/GuiTextEditPopup.cpp | 116 +++--- es-core/src/guis/GuiTextEditPopup.h | 34 +- es-core/src/resources/Font.cpp | 12 +- resources/systems/macos/es_systems.xml | 137 +++++-- resources/systems/unix/es_systems.xml | 135 +++++-- resources/systems/windows/es_systems.xml | 135 +++++-- themes/rbsimple-DE/theme.xml | 2 +- 53 files changed, 1555 insertions(+), 1316 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5557eb114..189efa79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,18 +10,23 @@ ### Detailed list of changes -* Added alternative emulators support where additional emulators can be defined in es_systems.xml and be selected system-wide or per game via the user interface +* Added alternative emulators support where additional emulators can be defined in es_systems.xml and be selected + system-wide or per game via the user interface * Added a virtual keyboard partly based on code from batocera-emulationstation -* Added the ability to make complementary game system customizations without having to replace the entire bundled es_systems.xml file +* Added the ability to make complementary game system customizations without having to replace the entire bundled + es_systems.xml file * Added a menu option to change the application exit key combination -* Expanded the themeable options for "helpsystem" to support custom button graphics, dimmed text and icon colors, upper/lower/camel case and custom spacing +* Expanded the themeable options for "helpsystem" to support custom button graphics, dimmed text and icon colors, + upper/lower/camel case and custom spacing * Added support for using the left and right trigger buttons in the help prompts * Removed the "Choose" entry from the help prompts in the gamelist view * Changed the "Toggle screensaver" help entry in the system view to simply "Screensaver" * Added support for upscaling bitmap images using linear filtering -* Changed the marquee image upscale filtering from nearest neighbor to linear for the launch screen and the gamelist views +* Changed the marquee image upscale filtering from nearest neighbor to linear for the launch screen and the gamelist + views * Moved the Media Viewer and Screensaver settings higher in the UI Settings menu -* Moved the game media directory setting to the top of the Other Settings menu, following the new Alternative Emulators entry +* Moved the game media directory setting to the top of the Other Settings menu, following the new Alternative Emulators + entry * Added a blinking cursor to TextEditComponent * Changed the filter description "Text filter (game name)" to "Game name" * Added support for a new type of "flat style" button to ButtonComponent @@ -31,7 +36,8 @@ * Replaced all built-in matrix and vector data types and functions with GLM library equivalents * Replaced some additional math functions and moved the remaining built-in functions to a math utility namespace * Added a function to generate MD5 hashes -* Moved the "complex" mode functionality from GuiComplexTextEditPopup into GuiTextEditPopup and removed the source files for the former +* Moved the "complex" mode functionality from GuiComplexTextEditPopup into GuiTextEditPopup and removed the source files + for the former * Increased the warning level for Clang/LLVM and GCC by adding -Wall, -Wpedantic and some additional flags * Fixed a lot of compiler warnings introduced by the -Wall and -Wpedantic flags * Changed the language standard from C++14 to C++17 @@ -40,23 +46,35 @@ ### Bug fixes -* When multi-scraping in interactive mode with "Auto-accept single game matches" enabled, the game name could not be refined if there were no games found -* When multi-scraping in interactive mode, the game counter was not decreased when skipping games, making it impossible to skip the final games in the queue +* When multi-scraping in interactive mode with "Auto-accept single game matches" enabled, the game name could not be + refined if there were no games found +* When multi-scraping in interactive mode, the game counter was not decreased when skipping games, making it impossible + to skip the final games in the queue * When multi-scraping in interactive mode, "No games found" results could be accepted using the "A" button -* When scraping in interactive mode, any refining done using the "Y" button shortcut would not be shown when doing another refine using the "Refine search" button -* Input consisting of only whitespace characters would get accepted by TextEditComponent which led to various strange behaviors -* Leading and trailing whitespace characters would not get trimmed from the collection name when creating a new custom collection +* When scraping in interactive mode, any refining done using the "Y" button shortcut would not be shown when doing + another refine using the "Refine search" button +* Input consisting of only whitespace characters would get accepted by TextEditComponent which led to various strange + behaviors +* Leading and trailing whitespace characters would not get trimmed from the collection name when creating a new custom + collection * Leading and trailing whitespace characters would get included in scraper search refines and TheGamesDB searches -* Game name (text) filters were matching the system names for collection systems if the "Show system names in collections" setting was enabled -* Brackets such as () and [] were filtered from game names in collection systems if the "Show system names in collections" setting was enabled +* Game name (text) filters were matching the system names for collection systems if the "Show system names in + collections" setting was enabled +* Brackets such as () and [] were filtered from game names in collection systems if the "Show system names in + collections" setting was enabled * When navigating menus, the separator lines and menu components did not align properly and moved up and down slightly -* When scrolling in menus, pressing other buttons than "Up" or "Down" did not stop the scrolling which caused all sorts of weird behavior -* With the menu scale-up effect enabled and entering a submenu before the parent menu was completely scaled up, the parent would get stuck at a semi-scaled size -* Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a gamelist on startup had been set -* When marking a game to not be counted in the metadata editor and the game was part of a custom collection, no collection disabling notification was displayed +* When scrolling in menus, pressing other buttons than "Up" or "Down" did not stop the scrolling which caused all sorts + of weird behavior +* With the menu scale-up effect enabled and entering a submenu before the parent menu was completely scaled up, the + parent would get stuck at a semi-scaled size +* Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a + gamelist on startup had been set +* When marking a game to not be counted in the metadata editor and the game was part of a custom collection, no + collection disabling notification was displayed * Horizontal sizing of the TextComponent input field was not consistent across different screen resolutions * The "sortname" window header was incorrectly spelled when editing this type of entry in the metadata editor -* When the last row of a menu had its text color changed, this color was completely desaturated when navigating to a button below the list +* When the last row of a menu had its text color changed, this color was completely desaturated when navigating to a + button below the list ## Version 1.1.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c8ff03b2..15beefa1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ set(LINUX_CPACK_GENERATOR "DEB" CACHE STRING "CPack generator, DEB or RPM") # Add local find modules to the CMake path. list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Utils - ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Packages) + ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Packages) # Define the options. option(GLES "Set to ON if targeting Embedded OpenGL" ${GLES}) @@ -36,11 +36,11 @@ option(CEC "Set to ON to enable CEC" ${CEC}) option(VLC_PLAYER "Set to ON to build the VLC-based video player" ${VLC_PLAYER}) option(CLANG_TIDY "Set to ON to build using the clang-tidy static analyzer" ${CLANG_TIDY}) -if(CLANG_TIDY) +if (CLANG_TIDY) find_program(CLANG_TIDY_BINARY NAMES clang-tidy) - if(CLANG_TIDY_BINARY STREQUAL "CLANG_TIDY_BINARY-NOTFOUND") + if (CLANG_TIDY_BINARY STREQUAL "CLANG_TIDY_BINARY-NOTFOUND") message("-- CLANG_TIDY was set but the clang-tidy binary was not found") - else() + else () message("-- Building with the clang-tidy static analyzer") set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*,\ -fuchsia-*,\ @@ -76,12 +76,12 @@ set_property(CACHE GLSYSTEM PROPERTY STRINGS "Desktop OpenGL" "Embedded OpenGL") #--------------------------------------------------------------------------------------------------- # Package dependencies. -if(GLSYSTEM MATCHES "Desktop OpenGL") +if (GLSYSTEM MATCHES "Desktop OpenGL") set(OpenGL_GL_PREFERENCE "GLVND") find_package(OpenGL REQUIRED) -else() +else () find_package(OpenGLES REQUIRED) -endif() +endif () # Skip package dependency checks if we're on Windows. if(NOT WIN32) @@ -103,73 +103,73 @@ if(CEC) endif() # Add ALSA for Linux. -if(CMAKE_SYSTEM_NAME MATCHES "Linux") +if (CMAKE_SYSTEM_NAME MATCHES "Linux") find_package(ALSA REQUIRED) -endif() +endif () #--------------------------------------------------------------------------------------------------- # Compiler and linker settings. -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") message("-- Compiler is Clang/LLVM") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0) message(SEND_ERROR "You need at least Clang 5.0.0 to compile EmulationStation-DE") - endif() -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + endif () +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") message("-- Compiler is GNU/GCC") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.1) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.1) message(SEND_ERROR "You need at least GCC 7.1 to compile EmulationStation-DE") - endif() - if(WIN32) + endif () + if (WIN32) set(CMAKE_CXX_FLAGS "-mwindows ${CMAKE_CXX_FLAGS}") - endif() -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + endif () +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") message("-- Compiler is MSVC") # If using the MSVC compiler on Windows, disable the built-in min() and max() macros. add_definitions(-DNOMINMAX) -endif() +endif () -if(CMAKE_BUILD_TYPE) +if (CMAKE_BUILD_TYPE) message("-- Build type is ${CMAKE_BUILD_TYPE}") -endif() +endif () # Set up compiler and linker flags for debug, profiling or release builds. if(CMAKE_BUILD_TYPE MATCHES Debug) # Enable the C++17 standard and disable optimizations as it's a debug build. - if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /Od /DEBUG:FULL") - else() + else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O0 -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0") - endif() + endif () # If using Clang, then add additional debug data needed by GDB. # Comment this out if you're using LLDB for debugging as this flag makes the binary # much larger and the application much slower. On macOS this setting is never enabled # as LLDB is the default debugger on this OS. - if(NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if (NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG") - endif() + endif () elseif(CMAKE_BUILD_TYPE MATCHES Profiling) # For the profiling build, we enable optimizations and supply the required profiler flags. - if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /O2 /DEBUG:FULL") - else() + else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -pg -g -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2 -pg") - endif() + endif () else() # Enable the C++17 standard and enable optimizations as it's a release build. # This will also disable all assert() macros. Strip the binary too. - if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG /std:c++17 /O2 /DEBUG:NONE") - else() + else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -DNDEBUG -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros") - if(APPLE) + if (APPLE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2") - else() + else () set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2 -s") - endif() - endif() + endif () + endif () endif() # The following removes half of the ranlib warnings on macOS regarding no symbols for files @@ -180,26 +180,26 @@ if(APPLE) endif() if(APPLE) - if(MACOS_CODESIGN_IDENTITY) + if (MACOS_CODESIGN_IDENTITY) message("-- Code signing certificate identity: " ${MACOS_CODESIGN_IDENTITY}) - endif() - if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) + endif () + if (CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) message("-- macOS version 10.13 or lower has been set, so if code signing is enabled, Hardened Runtime will not be used") - endif() + endif () endif() #--------------------------------------------------------------------------------------------------- # Preprocessor directives. -if(GLSYSTEM MATCHES "Desktop OpenGL") +if (GLSYSTEM MATCHES "Desktop OpenGL") add_definitions(-DUSE_OPENGL_21) -else() +else () add_definitions(-DUSE_OPENGLES_10) -endif() +endif () -if(VLC_PLAYER) +if (VLC_PLAYER) add_definitions(-DBUILD_VLC_PLAYER) -endif() +endif () if(DEFINED BCMHOST OR RPI) add_definitions(-D_RPI_) @@ -217,13 +217,13 @@ add_definitions(-DGLM_FORCE_XYZW_ONLY) # we use /usr on Linux, /usr/pkg on NetBSD and /usr/local on FreeBSD and OpenBSD. if(NOT WIN32 AND NOT APPLE) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - if(CMAKE_SYSTEM_NAME MATCHES "Linux") + if (CMAKE_SYSTEM_NAME MATCHES "Linux") set(CMAKE_INSTALL_PREFIX "/usr" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") - elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD") + elseif (CMAKE_SYSTEM_NAME MATCHES "NetBSD") set(CMAKE_INSTALL_PREFIX "/usr/pkg" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") - else() + else () set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") - endif() + endif () endif() message("-- Installation prefix is set to " ${CMAKE_INSTALL_PREFIX}) add_definitions(-DES_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}") @@ -239,16 +239,16 @@ endif() # Include files. set(COMMON_INCLUDE_DIRS ${CURL_INCLUDE_DIR} - ${FFMPEG_INCLUDE_DIRS} - ${FreeImage_INCLUDE_DIRS} - ${FREETYPE_INCLUDE_DIRS} - ${PUGIXML_INCLUDE_DIRS} - ${RAPIDJSON_INCLUDE_DIRS} - ${SDL2_INCLUDE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/external/CImg - ${CMAKE_CURRENT_SOURCE_DIR}/external/glm - ${CMAKE_CURRENT_SOURCE_DIR}/external/nanosvg/src - ${CMAKE_CURRENT_SOURCE_DIR}/es-core/src) + ${FFMPEG_INCLUDE_DIRS} + ${FreeImage_INCLUDE_DIRS} + ${FREETYPE_INCLUDE_DIRS} + ${PUGIXML_INCLUDE_DIRS} + ${RAPIDJSON_INCLUDE_DIRS} + ${SDL2_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/external/CImg + ${CMAKE_CURRENT_SOURCE_DIR}/external/glm + ${CMAKE_CURRENT_SOURCE_DIR}/external/nanosvg/src + ${CMAKE_CURRENT_SOURCE_DIR}/es-core/src) if(VLC_PLAYER) set(COMMON_INCLUDE_DIRS ${COMMON_INCLUDE_DIRS} ${VLC_INCLUDE_DIR}) endif() @@ -276,69 +276,69 @@ if(DEFINED libCEC_FOUND) endif() # For Linux, add the ALSA include directory. -if(CMAKE_SYSTEM_NAME MATCHES "Linux") +if (CMAKE_SYSTEM_NAME MATCHES "Linux") list(APPEND COMMON_INCLUDE_DIRS ${ALSA_INCLUDE_DIRS}) -endif() +endif () -if(DEFINED BCMHOST OR RPI) +if (DEFINED BCMHOST OR RPI) list(APPEND COMMON_INCLUDE_DIRS "${CMAKE_FIND_ROOT_PATH}/opt/vc/include" - "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos" - "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vmcs_host/linux" - "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos/pthreads") -endif() + "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos" + "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vmcs_host/linux" + "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos/pthreads") +endif () #--------------------------------------------------------------------------------------------------- # Dependency libraries. -if(NOT WIN32) +if (NOT WIN32) set(COMMON_LIBRARIES ${CURL_LIBRARIES} - ${FFMPEG_LIBRARIES} - ${FreeImage_LIBRARIES} - ${FREETYPE_LIBRARIES} - ${PUGIXML_LIBRARIES} - ${SDL2_LIBRARY}) - if(VLC_PLAYER) + ${FFMPEG_LIBRARIES} + ${FreeImage_LIBRARIES} + ${FREETYPE_LIBRARIES} + ${PUGIXML_LIBRARIES} + ${SDL2_LIBRARY}) + if (VLC_PLAYER) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${VLC_LIBRARIES}) - endif() -elseif(WIN32) - if(DEFINED MSVC) + endif () +elseif (WIN32) + if (DEFINED MSVC) set(COMMON_LIBRARIES "${PROJECT_SOURCE_DIR}/avcodec.lib" - "${PROJECT_SOURCE_DIR}/avfilter.lib" - "${PROJECT_SOURCE_DIR}/avformat.lib" - "${PROJECT_SOURCE_DIR}/avutil.lib" - "${PROJECT_SOURCE_DIR}/swresample.lib" - "${PROJECT_SOURCE_DIR}/swscale.lib" - "${PROJECT_SOURCE_DIR}/FreeImage.lib" - "${PROJECT_SOURCE_DIR}/glew32.lib" - "${PROJECT_SOURCE_DIR}/libcurl-x64.lib" - "${PROJECT_SOURCE_DIR}/freetype.lib" - "${PROJECT_SOURCE_DIR}/pugixml.lib" - "${PROJECT_SOURCE_DIR}/SDL2main.lib" - "${PROJECT_SOURCE_DIR}/SDL2.lib" - "Winmm.dll") - if(VLC_PLAYER) + "${PROJECT_SOURCE_DIR}/avfilter.lib" + "${PROJECT_SOURCE_DIR}/avformat.lib" + "${PROJECT_SOURCE_DIR}/avutil.lib" + "${PROJECT_SOURCE_DIR}/swresample.lib" + "${PROJECT_SOURCE_DIR}/swscale.lib" + "${PROJECT_SOURCE_DIR}/FreeImage.lib" + "${PROJECT_SOURCE_DIR}/glew32.lib" + "${PROJECT_SOURCE_DIR}/libcurl-x64.lib" + "${PROJECT_SOURCE_DIR}/freetype.lib" + "${PROJECT_SOURCE_DIR}/pugixml.lib" + "${PROJECT_SOURCE_DIR}/SDL2main.lib" + "${PROJECT_SOURCE_DIR}/SDL2.lib" + "Winmm.dll") + if (VLC_PLAYER) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "${PROJECT_SOURCE_DIR}/libvlc.lib") - endif() - else() + endif () + else () set(COMMON_LIBRARIES "${PROJECT_SOURCE_DIR}/avcodec-58.dll" - "${PROJECT_SOURCE_DIR}/avfilter-7.dll" - "${PROJECT_SOURCE_DIR}/avformat-58.dll" - "${PROJECT_SOURCE_DIR}/avutil-56.dll" - "${PROJECT_SOURCE_DIR}/swresample-3.dll" - "${PROJECT_SOURCE_DIR}/swscale-5.dll" - "${PROJECT_SOURCE_DIR}/FreeImage.dll" - "${PROJECT_SOURCE_DIR}/glew32.dll" - "${PROJECT_SOURCE_DIR}/libcurl-x64.dll" - "${PROJECT_SOURCE_DIR}/libfreetype.dll" - "${PROJECT_SOURCE_DIR}/libpugixml.dll" - "${PROJECT_SOURCE_DIR}/libSDL2main.a" - "${PROJECT_SOURCE_DIR}/SDL2.dll" - "mingw32" - "Winmm.dll") - if(VLC_PLAYER) + "${PROJECT_SOURCE_DIR}/avfilter-7.dll" + "${PROJECT_SOURCE_DIR}/avformat-58.dll" + "${PROJECT_SOURCE_DIR}/avutil-56.dll" + "${PROJECT_SOURCE_DIR}/swresample-3.dll" + "${PROJECT_SOURCE_DIR}/swscale-5.dll" + "${PROJECT_SOURCE_DIR}/FreeImage.dll" + "${PROJECT_SOURCE_DIR}/glew32.dll" + "${PROJECT_SOURCE_DIR}/libcurl-x64.dll" + "${PROJECT_SOURCE_DIR}/libfreetype.dll" + "${PROJECT_SOURCE_DIR}/libpugixml.dll" + "${PROJECT_SOURCE_DIR}/libSDL2main.a" + "${PROJECT_SOURCE_DIR}/SDL2.dll" + "mingw32" + "Winmm.dll") + if (VLC_PLAYER) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "${PROJECT_SOURCE_DIR}/libvlc.dll") - endif() - endif() + endif () + endif () endif() if(APPLE) @@ -366,9 +366,9 @@ if(DEFINED libCEC_FOUND) endif() # Add ALSA for Linux libraries. -if(CMAKE_SYSTEM_NAME MATCHES "Linux") +if (CMAKE_SYSTEM_NAME MATCHES "Linux") list(APPEND COMMON_LIBRARIES ${ALSA_LIBRARY}) -endif() +endif () if(DEFINED BCMHOST) link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vc/lib") @@ -378,11 +378,11 @@ elseif(RPI) list(APPEND COMMON_LIBRARIES ${OPENGLES_LIBRARIES}) endif() -if(GLSYSTEM MATCHES "Desktop OpenGL") +if (GLSYSTEM MATCHES "Desktop OpenGL") list(APPEND COMMON_LIBRARIES ${OPENGL_LIBRARIES}) -else() +else () list(APPEND COMMON_LIBRARIES EGL ${OPENGLES_LIBRARIES}) -endif() +endif () #--------------------------------------------------------------------------------------------------- # Build directories. diff --git a/CREDITS.md b/CREDITS.md index 60cac7b99..770648677 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -60,7 +60,6 @@ https://rapidjson.org SDL \ https://www.libsdl.org - # Code Some code (like the virtual keyboard) was borrowed from Batocera.linux \ @@ -72,7 +71,6 @@ https://www.bzflag.org A few of the GLSL shaders were borrowed from the RetroArch project \ https://www.retroarch.com - # Resources Akrobat font \ diff --git a/INSTALL-DEV.md b/INSTALL-DEV.md index e7db548c6..b313d020c 100644 --- a/INSTALL-DEV.md +++ b/INSTALL-DEV.md @@ -125,7 +125,9 @@ pkg_add vlc In the same manner as for FreeBSD, Clang/LLVM and cURL should already be installed by default. -RapidJSON is not part of the OpenBSD ports/package collection as of v6.8, so you need to compile it yourself. At the time of writing, the latest release v1.1.0 does not compile on OpenBSD, so you need to use the latest available code from the master branch: +RapidJSON is not part of the OpenBSD ports/package collection as of v6.8, so you need to compile it yourself. At the +time of writing, the latest release v1.1.0 does not compile on OpenBSD, so you need to use the latest available code +from the master branch: ``` git clone https://github.com/Tencent/rapidjson.git @@ -865,6 +867,7 @@ nmake ``` MinGW: + ``` cmake -G "MinGW Makefiles" -DBUILD_SHARED_LIBS=ON . make @@ -874,14 +877,13 @@ make For RapidJSON you don't need to compile anything, you just need the include files. -At the time of writing, the latest release v1.1.0 generates some compiler warnings on Windows, but this can be avoided by using the latest available code from the master branch: +At the time of writing, the latest release v1.1.0 generates some compiler warnings on Windows, but this can be avoided +by using the latest available code from the master branch: ``` git clone git://github.com/Tencent/rapidjson.git ``` - - **Clone the ES-DE repository:** This works the same as on Unix or macOS, just run the following: @@ -1396,24 +1398,44 @@ For the following options, the es_settings.xml file is immediately updated/saved --show-hidden-games ``` - ## es_systems.xml -The es_systems.xml file contains the game systems configuration data for ES-DE, written in XML format. This defines the system name, the full system name, the ROM path, the allowed file extensions, the launch command, the platform (for scraping) and the theme to use. +The es_systems.xml file contains the game systems configuration data for ES-DE, written in XML format. This defines the +system name, the full system name, the ROM path, the allowed file extensions, the launch command, the platform (for +scraping) and the theme to use. -ES-DE ships with a comprehensive `es_systems.xml` file and most users will probably never need to make any customizations. But there may be special circumstances such as wanting to use different emulators for some game systems or perhaps to add additional systems altogether. +ES-DE ships with a comprehensive `es_systems.xml` file and most users will probably never need to make any +customizations. But there may be special circumstances such as wanting to use different emulators for some game systems +or perhaps to add additional systems altogether. -To accomplish this, ES-DE supports customizations via a separate es_systems.xml file that is to be placed in the `custom_systems` folder in the application home directory, i.e. `~/.emulationstation/custom_systems/es_systems.xml`. (The tilde symbol `~` translates to `$HOME` on Unix and macOS, and to `%HOMEPATH%` on Windows unless overridden via the --home command line option.) +To accomplish this, ES-DE supports customizations via a separate es_systems.xml file that is to be placed in +the `custom_systems` folder in the application home directory, i.e. `~/.emulationstation/custom_systems/es_systems.xml` +. (The tilde symbol `~` translates to `$HOME` on Unix and macOS, and to `%HOMEPATH%` on Windows unless overridden via +the --home command line option.) -This custom file functionality is designed to be complementary to the bundled es_systems.xml file, meaning you should only add entries to the custom configuration file for game systems that you actually want to add or override. So to for example customize a single system, this file should only contain a single `` tag. The structure of the custom file is identical to the bundled file with the exception of an additional optional tag named ``. If this is placed in the custom es_systems.xml file, ES-DE will not load the bundled file. This is normally not recommended and should only be used for special situations. At the end of this section you can find an example of a custom es_systems.xml file. +This custom file functionality is designed to be complementary to the bundled es_systems.xml file, meaning you should +only add entries to the custom configuration file for game systems that you actually want to add or override. So to for +example customize a single system, this file should only contain a single `` tag. The structure of the custom +file is identical to the bundled file with the exception of an additional optional tag named ``. If this +is placed in the custom es_systems.xml file, ES-DE will not load the bundled file. This is normally not recommended and +should only be used for special situations. At the end of this section you can find an example of a custom +es_systems.xml file. -The bundled es_systems.xml file is located in the resources directory that is part of the application installation. For example this could be `/usr/share/emulationstation/resources/systems/unix/es_systems.xml` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/resources/systems/macos/es_systems.xml` on macOS or `C:\Program Files\EmulationStation-DE\resources\systems\windows\es_systems.xml` on Windows. The actual location may differ from these examples of course, depending on where ES-DE has been installed. +The bundled es_systems.xml file is located in the resources directory that is part of the application installation. For +example this could be `/usr/share/emulationstation/resources/systems/unix/es_systems.xml` on +Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/resources/systems/macos/es_systems.xml` on +macOS or `C:\Program Files\EmulationStation-DE\resources\systems\windows\es_systems.xml` on Windows. The actual location +may differ from these examples of course, depending on where ES-DE has been installed. -It doesn't matter in which order you define the systems as they will be sorted by the full system name inside the application, but it's still probably a good idea to add them in alphabetical order to make the file easier to maintain. +It doesn't matter in which order you define the systems as they will be sorted by the full system name inside the +application, but it's still probably a good idea to add them in alphabetical order to make the file easier to maintain. -Keep in mind that you have to set up your emulators separately from ES-DE as the es_systems.xml file assumes that your emulator environment is properly configured. +Keep in mind that you have to set up your emulators separately from ES-DE as the es_systems.xml file assumes that your +emulator environment is properly configured. -Below is an overview of the file layout with various examples. For the command tag, the newer es_find_rules.xml logic described later in this document removes the need for most of the legacy options, but they are still supported for special configurations and for backward compatibility with old configuration files. +Below is an overview of the file layout with various examples. For the command tag, the newer es_find_rules.xml logic +described later in this document removes the need for most of the legacy options, but they are still supported for +special configurations and for backward compatibility with old configuration files. ```xml @@ -1568,7 +1590,9 @@ And finally one for Windows: ``` -As well, here's an example for Unix of a custom es_systems.xml file placed in ~/.emulationstation/custom_systems/ that overrides a single game system from the bundled configuration file: +As well, here's an example for Unix of a custom es_systems.xml file placed in ~/.emulationstation/custom_systems/ that +overrides a single game system from the bundled configuration file: + ```xml @@ -1585,7 +1609,8 @@ As well, here's an example for Unix of a custom es_systems.xml file placed in ~/ ``` -If adding the `` tag to the file, the bundled es_systems.xml file will not be processed. For this example it wouldn't be a very good idea as NES would then be the only platform that could be used in ES-DE. +If adding the `` tag to the file, the bundled es_systems.xml file will not be processed. For this +example it wouldn't be a very good idea as NES would then be the only platform that could be used in ES-DE. ```xml @@ -1608,9 +1633,11 @@ If adding the `` tag to the file, the bundled es_systems.xml fil This file makes it possible to define rules for where to search for the emulator binaries and emulator cores. -The file is located in the resources directory in the same location as the es_systems.xml file, but a customized copy can be placed in ~/.emulationstation/custom_systems, which will override the bundled file. +The file is located in the resources directory in the same location as the es_systems.xml file, but a customized copy +can be placed in ~/.emulationstation/custom_systems, which will override the bundled file. Here's an example es_find_rules.xml file for Unix: + ```xml diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 4d75dc06b..c6ced13e0 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -73,17 +73,37 @@ There's not really much to say about these operating systems, just install ES-DE Upon first startup, ES-DE will create its `~/.emulationstation` home directory. -On Unix this means /home/\/.emulationstation/, on macOS /Users/\/.emulationstation/ and on Windows C:\Users\\\\.emulationstation\ +On Unix this means /home/\/.emulationstation/, on macOS /Users/\/.emulationstation/ and on Windows +C:\Users\\\\.emulationstation\ -**Note:** As of ES-DE v1.1 there is no internationalization support, which means that the application will always require the physical rather than the localized path to your home directory. For instance on macOS configured for the Swedish language /Users/myusername will be the physical path but /Användare/myusername is the localized path that is actually shown in the user interface. The same is true on Windows where the directories would be C:\Users\myusername and C:\Användare\myusername respectively. If attempting to enter the localized path for any directory-related setting, ES-DE will not be able to find it. But it's always possible to use the tilde `~` symbol when referring to your home directory, which ES-DE will expand to the physical location regardless of what language you have configured for your operating system. If you're using an English-localized system, this whole point is irrelevant as the physical and localized paths are then identical. +**Note:** As of ES-DE v1.1 there is no internationalization support, which means that the application will always +require the physical rather than the localized path to your home directory. For instance on macOS configured for the +Swedish language /Users/myusername will be the physical path but /Användare/myusername is the localized path that is +actually shown in the user interface. The same is true on Windows where the directories would be C:\Users\myusername and +C:\Användare\myusername respectively. If attempting to enter the localized path for any directory-related setting, ES-DE +will not be able to find it. But it's always possible to use the tilde `~` symbol when referring to your home directory, +which ES-DE will expand to the physical location regardless of what language you have configured for your operating +system. If you're using an English-localized system, this whole point is irrelevant as the physical and localized paths +are then identical. -It's possible to override the home directory path using the --home command line option, but this is normally required only for very special situations so we can safely ignore that option for now. +It's possible to override the home directory path using the --home command line option, but this is normally required +only for very special situations so we can safely ignore that option for now. -Also on first startup the configuration file `es_settings.xml` will be generated in the ES-DE home directory, containing all the application settings at their default values. Following this, a file named `es_systems.xml` will be loaded from the resources directory (which is part of the ES-DE installation). This file contains the game system definitions including which emulator to use per platform. For some systems there are also alternative emulators defined which can be applied system-wide or per game. How that works is explained later in this guide. A customized systems configuration file can also be used, as described in the next section. +Also on first startup the configuration file `es_settings.xml` will be generated in the ES-DE home directory, containing +all the application settings at their default values. Following this, a file named `es_systems.xml` will be loaded from +the resources directory (which is part of the ES-DE installation). This file contains the game system definitions +including which emulator to use per platform. For some systems there are also alternative emulators defined which can be +applied system-wide or per game. How that works is explained later in this guide. A customized systems configuration +file can also be used, as described in the next section. -There's an application log file created in the ES-DE home directory named `es_log.txt`, please refer to this in case of any issues as it should hopefully provide information on what went wrong. Starting ES-DE with the --debug flag provides even more detailed information. +There's an application log file created in the ES-DE home directory named `es_log.txt`, please refer to this in case of +any issues as it should hopefully provide information on what went wrong. Starting ES-DE with the --debug flag provides +even more detailed information. -After ES-DE finds at least one game file, it will populate that game system and the application will start. If there are no game files, a dialog will be shown explaining that you need to install your game files into your ROMs directory. You will also be given a choice to change that ROMs directory path if you don't want to use the default one. As well you have the option to generate the complete game systems directory structure based on information in es_systems.xml. +After ES-DE finds at least one game file, it will populate that game system and the application will start. If there are +no game files, a dialog will be shown explaining that you need to install your game files into your ROMs directory. You +will also be given a choice to change that ROMs directory path if you don't want to use the default one. As well you +have the option to generate the complete game systems directory structure based on information in es_systems.xml. When generating the directory structure, a file named systeminfo.txt will be created in each game system folder which will provide you with some information about the system. Here's an example for the _gc_ system as seen on macOS: ``` @@ -106,9 +126,11 @@ Theme folder: gc ``` -The primary use of this file is to see which RetroArch core the system needs, which you will have to install manually from inside the RetroArch user interface. Also the supported file extensions can be quite useful to know. +The primary use of this file is to see which RetroArch core the system needs, which you will have to install manually +from inside the RetroArch user interface. Also the supported file extensions can be quite useful to know. -In addition to this, a file named systems.txt will be created in the root of the ROMs directory which shows the mapping between the directory names and the full system names. +In addition to this, a file named systems.txt will be created in the root of the ROMs directory which shows the mapping +between the directory names and the full system names. For example: @@ -118,32 +140,59 @@ genesis: Sega Genesis gx4000: Amstrad GX4000 ``` -If a custom es_systems.xml file is present in ~/.emulationstation/custom_systems/ any entries from this file will have their names trailed by the text _(custom system)_. So if the GameCube system in the example above would be present in the custom systems configuration file, the system would be shown as `gc (custom system)` instead of simply `gc`. This is only applicable for the systems.txt and systeminfo.txt files, the trailing text is not applied or used anywhere else in the application. +If a custom es_systems.xml file is present in ~/.emulationstation/custom_systems/ any entries from this file will have +their names trailed by the text _(custom system)_. So if the GameCube system in the example above would be present in +the custom systems configuration file, the system would be shown as `gc (custom system)` instead of simply `gc`. This is +only applicable for the systems.txt and systeminfo.txt files, the trailing text is not applied or used anywhere else in +the application. -Note that neither the systeminfo.txt files or the systems.txt file are needed to run ES-DE, they're only generated as a convenience to help with the setup. +Note that neither the systeminfo.txt files or the systems.txt file are needed to run ES-DE, they're only generated as a +convenience to help with the setup. -There will be a lot of directories created if using the es_systems.xml file bundled with the installation, so it may be a good idea to remove the ones you don't need. It's recommended to move them to another location to be able to use them later if more systems should be added. For example a directory named _DISABLED could be created inside the ROMs folder (i.e. ~/ROMs/_DISABLED) and all game system directories you don't need could be moved there. Doing this reduces the application startup time as ES-DE would otherwise need to scan for game files for all these systems. +There will be a lot of directories created if using the es_systems.xml file bundled with the installation, so it may be +a good idea to remove the ones you don't need. It's recommended to move them to another location to be able to use them +later if more systems should be added. For example a directory named _DISABLED could be created inside the ROMs folder ( +i.e. ~/ROMs/_DISABLED) and all game system directories you don't need could be moved there. Doing this reduces the +application startup time as ES-DE would otherwise need to scan for game files for all these systems. ![alt text](images/es-de_ui_easy_setup.png "ES-DE Easy Setup") -_This is the dialog shown if no game files were found. It lets you configure the ROM directory if you don't want to use the default one, and you can also generate the game systems directory structure. Note that the directory is the physical path, and that your operating system may present this as a localized path if you are using a language other than English._ - +_This is the dialog shown if no game files were found. It lets you configure the ROM directory if you don't want to use +the default one, and you can also generate the game systems directory structure. Note that the directory is the physical +path, and that your operating system may present this as a localized path if you are using a language other than +English._ ## Game system customizations -The game systems configuration file `es_systems.xml` is located in the ES-DE resources directory which is part of the application installation. As such this file is not intended to be modified directly. If system customizations are required, a separate es_systems.xml file should instead be placed in the `custom_systems` folder in the ES-DE home directory, i.e. `~/.emulationstation/custom_systems/es_systems.xml`. +The game systems configuration file `es_systems.xml` is located in the ES-DE resources directory which is part of the +application installation. As such this file is not intended to be modified directly. If system customizations are +required, a separate es_systems.xml file should instead be placed in the `custom_systems` folder in the ES-DE home +directory, i.e. `~/.emulationstation/custom_systems/es_systems.xml`. -Although it's possible to make a copy of the bundled configuration file, to modify it and then place it in this directory, that is not how the system customization is designed to be done. Instead the intention is that the file in the custom_systems directory complements the bundled configuration, meaning only systems that are to be modified should be included. +Although it's possible to make a copy of the bundled configuration file, to modify it and then place it in this +directory, that is not how the system customization is designed to be done. Instead the intention is that the file in +the custom_systems directory complements the bundled configuration, meaning only systems that are to be modified should +be included. -For example you may want to replace the emulator launch command, modify the full name or change the supported file extensions for a single system. In this case it wouldn't make sense to copy the complete bundled file and just apply these minor modifications, instead an es_systems.xml file only containing the configuration for that single system should be placed in the custom_systems directory. - -The instructions for how to customize the es_systems.xml file can be found in [INSTALL-DEV.md](INSTALL-DEV.md#es_systemsxml). There you can also find an example of a custom file that you can copy into ~/.emulationstation/custom_systems/ and modify as required. +For example you may want to replace the emulator launch command, modify the full name or change the supported file +extensions for a single system. In this case it wouldn't make sense to copy the complete bundled file and just apply +these minor modifications, instead an es_systems.xml file only containing the configuration for that single system +should be placed in the custom_systems directory. +The instructions for how to customize the es_systems.xml file can be found +in [INSTALL-DEV.md](INSTALL-DEV.md#es_systemsxml). There you can also find an example of a custom file that you can copy +into ~/.emulationstation/custom_systems/ and modify as required. ## Migrating from other EmulationStation forks **IMPORTANT!!! IMPORTANT!!! IMPORTANT!!!** -ES-DE is designed to be backward compatible to a certain degree, that is, it should be able to read data from other/previous EmulationStation versions such as the RetroPie fork. But the opposite is not true and it's a one-way ticket for your gamelist.xml files and your custom collection files when migrating to ES-DE as they will be modified in ways that previous ES versions will see as data loss. For instance ES-DE does not use image tags inside the gamelist.xml files to find game media but instead matches the media to the names of the game/ROM files. So it will not save any such tags back to the gamelist files during updates, effectively removing the display of the game media if the files are opened in another ES fork. +ES-DE is designed to be backward compatible to a certain degree, that is, it should be able to read data from +other/previous EmulationStation versions such as the RetroPie fork. But the opposite is not true and it's a one-way +ticket for your gamelist.xml files and your custom collection files when migrating to ES-DE as they will be modified in +ways that previous ES versions will see as data loss. For instance ES-DE does not use image tags inside the gamelist.xml +files to find game media but instead matches the media to the names of the game/ROM files. So it will not save any such +tags back to the gamelist files during updates, effectively removing the display of the game media if the files are +opened in another ES fork. Due to this, always make backups of at least the following directories before testing ES-DE for the first time: @@ -377,7 +426,8 @@ The platform name for the Commodore 64 is `c64`, so the following structure woul ~/ROMs/c64/Multidisk/Pirates/Pirates!.m3u ``` -It's highly recommended to create `.m3u` playlist files for multi-disc images as this normally automates disk swapping in the emulator. It's then this .m3u file that should be selected for launching the game. +It's highly recommended to create `.m3u` playlist files for multi-disc images as this normally automates disk swapping +in the emulator. It's then this .m3u file that should be selected for launching the game. The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2.m3u: @@ -430,9 +480,13 @@ Apart from the potential difficulty in locating the emulator binary, there are s #### Commodore Amiga -There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play Amiga games as any console with game ROMs. +There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard +disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play +Amiga games as any console with game ROMs. -An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can either put single-disc images in the root folder or in a dedicated adf directory, or multiple-disk games in separate folders. It's highly recommended to create `.m3u` playlist files for multi-disc images as described earlier. +An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can +either put single-disc images in the root folder or in a dedicated adf directory, or multiple-disk games in separate +folders. It's highly recommended to create `.m3u` playlist files for multi-disc images as described earlier. Here's an example of what the file structure could look like: @@ -872,7 +926,8 @@ If this setting is enabled and a folder has its flag set to be excluded from the **Scrape actual folders** _(Multi-scraper only)_ -Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any multi-disc games where there is a folder for each individual game. +Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any +multi-disc games where there is a folder for each individual game. **Auto-retry on peer verification errors** _(ScreenScraper only)_ @@ -944,27 +999,50 @@ Whether to sort your favorite games above your other games in the gamelists. **Add star markings to favorite games** -With this setting enabled, there is a star symbol added at the beginning of the game name in the gamelist views. It's strongly recommended to keep this setting enabled if the option to sort favorite games above non-favorites has been enabled. If not, favorite games would be sorted on top of the gamelist with no visual indication that they are favorites, which would be quite confusing. +With this setting enabled, there is a star symbol added at the beginning of the game name in the gamelist views. It's +strongly recommended to keep this setting enabled if the option to sort favorite games above non-favorites has been +enabled. If not, favorite games would be sorted on top of the gamelist with no visual indication that they are +favorites, which would be quite confusing. **Use plain ASCII for special gamelist characters** -There are some special characters in ES-DE such as the favorites star, the folder icon and the tickmark (seen when editing custom collections) that are displayed using symbols from the bundled Font Awesome. This normally looks perfectly fine, but on some specific theme sets with very pixelated fonts such as [es-themes-snes-mini](https://github.com/ruckage/es-theme-snes-mini) and [es-themes-workbench](https://github.com/ehettervik/es-theme-workbench) these symbols look terrible. For such themes, this option is available, which will use plain ASCII characters instead of the Font Awesome symbols. For the favorites an asterisk `*` will be used, for folders a hash sign `#` will be used and for the tickmark an exclamation mark `!` will be used. This only applies to the gamelist view, in all other places in the application the Font Awesome symbols are retained. Make sure to disable this option if not using such a pixelated theme as it looks equally terrible to enable this option on themes where it's not supposed to be used. +There are some special characters in ES-DE such as the favorites star, the folder icon and the tickmark (seen when +editing custom collections) that are displayed using symbols from the bundled Font Awesome. This normally looks +perfectly fine, but on some specific theme sets with very pixelated fonts such +as [es-themes-snes-mini](https://github.com/ruckage/es-theme-snes-mini) +and [es-themes-workbench](https://github.com/ehettervik/es-theme-workbench) these symbols look terrible. For such +themes, this option is available, which will use plain ASCII characters instead of the Font Awesome symbols. For the +favorites an asterisk `*` will be used, for folders a hash sign `#` will be used and for the tickmark an exclamation +mark `!` will be used. This only applies to the gamelist view, in all other places in the application the Font Awesome +symbols are retained. Make sure to disable this option if not using such a pixelated theme as it looks equally terrible +to enable this option on themes where it's not supposed to be used. **Enable quick list scrolling overlay** -With this option enabled, there will be an overlay displayed when scrolling the gamelists quickly, i.e. when holding down the _Up_, _Down_, _Left shoulder_ or _Right shoulder_ buttons for some time. The overlay will darken the background slightly and display the first two characters of the game names. If the game is a favorite and the setting to sort favorites above non-favorites has been enabled, a star will be shown instead. +With this option enabled, there will be an overlay displayed when scrolling the gamelists quickly, i.e. when holding +down the _Up_, _Down_, _Left shoulder_ or _Right shoulder_ buttons for some time. The overlay will darken the background +slightly and display the first two characters of the game names. If the game is a favorite and the setting to sort +favorites above non-favorites has been enabled, a star will be shown instead. **Enable virtual keyboard** -This enables a virtual (on-screen) keyboard that can be used at various places throughout the application to input text and numbers using a controller. The Shift and Alt keys can be toggled individually or combined together to access many special characters. The general use of the virtual keyboard should hopefully be self-explanatory. +This enables a virtual (on-screen) keyboard that can be used at various places throughout the application to input text +and numbers using a controller. The Shift and Alt keys can be toggled individually or combined together to access many +special characters. The general use of the virtual keyboard should hopefully be self-explanatory. **Enable toggle favorites button** -This setting enables the _Y_ button for quickly toggling a game as favorite. Although this may be convenient at times, it's also quite easy to accidentally remove a favorite tagging of a game when using the application more casually. As such it could sometimes make sense to disable this functionality. It's of course still possible to mark a game as favorite using the metadata editor when this setting is disabled. The option does not affect the use of the _Y_ button to add or remove games when editing custom collections. +This setting enables the _Y_ button for quickly toggling a game as favorite. Although this may be convenient at times, +it's also quite easy to accidentally remove a favorite tagging of a game when using the application more casually. As +such it could sometimes make sense to disable this functionality. It's of course still possible to mark a game as +favorite using the metadata editor when this setting is disabled. The option does not affect the use of the _Y_ button +to add or remove games when editing custom collections. **Enable random system or game button** -This enables or disables the ability to jump to a random system or game. It's mapped to the thumbstick click button, either the left or right thumbstick will work. The help prompts will also visually indicate whether this option is enabled or not. +This enables or disables the ability to jump to a random system or game. It's mapped to the thumbstick click button, +either the left or right thumbstick will work. The help prompts will also visually indicate whether this option is +enabled or not. **Enable gamelist filters** @@ -1242,7 +1320,11 @@ If this option is disabled, hidden files and folders within the ROMs directory t **Show hidden games (requires restart)** -You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want to see some batch files and executables inside ES-DE, or for multi-disc games where you may only want to show the .m3u playlists and not the individual game files. By disabling this option these files will not be processed at all when ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in the gamelist view to visually indicate that they are hidden. +You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want +to see some batch files and executables inside ES-DE, or for multi-disc games where you may only want to show the .m3u +playlists and not the individual game files. By disabling this option these files will not be processed at all when +ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in +the gamelist view to visually indicate that they are hidden. **Enable custom event scripts** @@ -1350,9 +1432,14 @@ The following filters can be applied: **Hidden** -With the exception of the game name text filter, all available filter values are assembled from metadata from the actual gamelist, so if there for instance are no games marked as completed, the Completed filter will only have the selectable option False, i.e. True will be missing. +With the exception of the game name text filter, all available filter values are assembled from metadata from the actual +gamelist, so if there for instance are no games marked as completed, the Completed filter will only have the selectable +option False, i.e. True will be missing. -Be aware that although folders can have most of the metadata values set, the filters are only applied to files (this is also true for the game name text filter). So if you for example set a filter to only display your favorite games, any folder that contains a favorite game will be displayed, and other folders which are themselves marked as favorites but that do not contain any favorite games will be hidden. +Be aware that although folders can have most of the metadata values set, the filters are only applied to files (this is +also true for the game name text filter). So if you for example set a filter to only display your favorite games, any +folder that contains a favorite game will be displayed, and other folders which are themselves marked as favorites but +that do not contain any favorite games will be hidden. The filters are always applied for the complete game system, including all folder content. @@ -1430,7 +1517,11 @@ A flag to mark whether the game is suitable for children. This will be applied a **Hidden** -A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game files for multi-disc games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden entry. +A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not +be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game +files for multi-disc games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games +has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden +entry. **Broken/not working** @@ -1438,15 +1529,27 @@ A flag to indicate whether the game is broken. Useful for MAME games for instanc **Exclude from game counter** _(files only)_ -A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be included in the game counter shown per system on the system view, and it will not be included in the system information field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite useful for multi-file games such as multi-disc Amiga or Commodore 64 games, or for DOS games where you want to exclude setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this flag set will have a lower opacity in the gamelists, making them easy to spot. +A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be +included in the game counter shown per system on the system view, and it will not be included in the system information +field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite +useful for multi-file games such as multi-disc Amiga or Commodore 64 games, or for DOS games where you want to exclude +setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this +flag set will have a lower opacity in the gamelists, making them easy to spot. **Exclude from multi-scraper** -Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for multi-disc games for example. There is an option in the scraper settings to ignore this flag, but by default the multi-scraper will respect it. +Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for +multi-disc games for example. There is an option in the scraper settings to ignore this flag, but by default the +multi-scraper will respect it. **Hide metadata fields** -This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. +This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for +situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or +INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u +playlist should have any metadata values. The only fields shown with this option enabled are the game name and +description. Using the description it's possible to write some comments regarding the file or folder, should you want +to. It's also possible to display game images and videos with this setting enabled. **Times played** _(files only)_ @@ -1632,35 +1735,82 @@ Refer to the [INSTALL-DEV.md](INSTALL-DEV.md#command-line-options) document for ## Supported game systems -**Note:** The following list is what the default es_systems.xml files and the rbsimple-DE theme supports. This theme set is very comprehensive, so if you're using another theme, it may be that some or many of these systems are not supported. ES-DE will still work but the game system will unthemed which looks very ugly. +**Note:** The following list is what the default es_systems.xml files and the rbsimple-DE theme supports. This theme set +is very comprehensive, so if you're using another theme, it may be that some or many of these systems are not supported. +ES-DE will still work but the game system will unthemed which looks very ugly. -Note as well that the list and corresponding es_systems.xml templates may not reflect what is readily available for all supported operating system. This is especially true on Unix/Linux if installing RetroArch via the OS repository instead of using the Snap or Flatpak distributions (or compiling from source code) as the repository versions are normally quite crippled. +Note as well that the list and corresponding es_systems.xml templates may not reflect what is readily available for all +supported operating system. This is especially true on Unix/Linux if installing RetroArch via the OS repository instead +of using the Snap or Flatpak distributions (or compiling from source code) as the repository versions are normally quite +crippled. -The column **System name** corresponds to the directory where you should put your game files, e.g. `~/ROMs/c64` or `~/ROMs/megadrive`. +The column **System name** corresponds to the directory where you should put your game files, e.g. `~/ROMs/c64` +or `~/ROMs/megadrive`. -Regional differences are handled by simply using the game system name corresponding to your region. For example for Sega Mega Drive, _megadrive_ would be used by most people in the world, although persons from North America would use _genesis_ instead. The same is true for _pcengine_ vs _tg16_ etc. This only affects the theme selection and the corresponding theme graphics, the same emulator and scraper settings are still used for the regional variants although that can of course be customized in the es_systems.xml file if you wish. +Regional differences are handled by simply using the game system name corresponding to your region. For example for Sega +Mega Drive, _megadrive_ would be used by most people in the world, although persons from North America would use _ +genesis_ instead. The same is true for _pcengine_ vs _tg16_ etc. This only affects the theme selection and the +corresponding theme graphics, the same emulator and scraper settings are still used for the regional variants although +that can of course be customized in the es_systems.xml file if you wish. -Sometimes the name of the console is (more or less) the same for multiple regions, and in those cases the region has been added as a suffix to the game system name. For instance `na` for North America has been added to `snes` (Super Nintendo) giving the system name `snesna`. The same goes for Japan, as in `megacd` and `megacdjp`. Again, this only affects the theme and theme graphics. +Sometimes the name of the console is (more or less) the same for multiple regions, and in those cases the region has +been added as a suffix to the game system name. For instance `na` for North America has been added to `snes` (Super +Nintendo) giving the system name `snesna`. The same goes for Japan, as in `megacd` and `megacdjp`. Again, this only +affects the theme and theme graphics. For the **Full name** column, text inside square brackets [] are comments and not part of the actual system name. -The **Default emulator** column lists the primary emulator as configured in es_systems.xml. If this differs between Unix, macOS and Windows then it's specified in square brackets, such as [UW] for Unix and Windows and [M] for macOS. If not all of the three platforms are specified it means that the system is not available on the missing platforms. For example Lutris which is only avaialable on Unix is marked with only a _[U]_. Unless explicitly marked as **(Standalone)**, each emulator is a RetroArch core. +The **Default emulator** column lists the primary emulator as configured in es_systems.xml. If this differs between +Unix, macOS and Windows then it's specified in square brackets, such as [UW] for Unix and Windows and [M] for macOS. If +not all of the three platforms are specified it means that the system is not available on the missing platforms. For +example Lutris which is only avaialable on Unix is marked with only a _[U]_. Unless explicitly marked as **( +Standalone)**, each emulator is a RetroArch core. -The **Alternative emulators** column lists additional emulators configured in es_systems.xml that can be selected per system and per game, as explained earlier in this guide. This does not necessarily include everything in existence, as for some platforms there are a lot of emulators to choose from. In those cases the included emulators is a curated selection. In the same manner as the _Default emulator_ column, differences between Unix, macOS and Windows are marked using square brackets. Unless explicitly marked as **(Standalone)**, echo emulator is a RetroArch core. +The **Alternative emulators** column lists additional emulators configured in es_systems.xml that can be selected per +system and per game, as explained earlier in this guide. This does not necessarily include everything in existence, as +for some platforms there are a lot of emulators to choose from. In those cases the included emulators is a curated +selection. In the same manner as the _Default emulator_ column, differences between Unix, macOS and Windows are marked +using square brackets. Unless explicitly marked as **(Standalone)**, echo emulator is a RetroArch core. -The **Needs BIOS** column indicates if additional BIOS/system ROMs are required, as should be explained by the emulator documentation. Good starting points for such documentation are [https://docs.libretro.com](https://docs.libretro.com) and [https://docs.libretro.com/library/bios](https://docs.libretro.com/library/bios) +The **Needs BIOS** column indicates if additional BIOS/system ROMs are required, as should be explained by the emulator +documentation. Good starting points for such documentation are [https://docs.libretro.com](https://docs.libretro.com) +and [https://docs.libretro.com/library/bios](https://docs.libretro.com/library/bios) -For additional details regarding which game file extensions are supported per system, refer to the es_systems.xml files [unix/es_systems.xml](resources/systems/unix/es_systems.xml), [macos/es_systems.xml](resources/systems/macos/es_systems.xml) and [windows/es_systems.xml](resources/systems/windows/es_systems.xml). Normally the extensions setup in these files should cover everything that the emulators support. Note that for systems that have alternative emulators defined, the list of extensions is a combination of what is supported by all the emulators. This approach is necessary as you want to be able to see all games for each system while potentially testing and switching between different emulators, either system-wide or on a per game basis. +For additional details regarding which game file extensions are supported per system, refer to the es_systems.xml +files [unix/es_systems.xml](resources/systems/unix/es_systems.xml) +, [macos/es_systems.xml](resources/systems/macos/es_systems.xml) +and [windows/es_systems.xml](resources/systems/windows/es_systems.xml). Normally the extensions setup in these files +should cover everything that the emulators support. Note that for systems that have alternative emulators defined, the +list of extensions is a combination of what is supported by all the emulators. This approach is necessary as you want to +be able to see all games for each system while potentially testing and switching between different emulators, either +system-wide or on a per game basis. -If you generated the ROMs directory structure when first starting ES-DE, the systeminfo.txt files located in each game system directory will also contain the information about the emulator core and supported file extensions. +If you generated the ROMs directory structure when first starting ES-DE, the systeminfo.txt files located in each game +system directory will also contain the information about the emulator core and supported file extensions. -For CD-based systems it's generally recommended to use CHD files (extension .chd) as this saves space due to compression compared to BIN/CUE, IMG, ISO etc. The CHD format is also supported by most emulators. You can convert to CHD from various formats using the MAME `chdman` utility, for example `chdman createcd -i mygame.iso -o mygame.chd`. Sometimes chdman has issues converting from the IMG and BIN formats, and in this case it's possible to first convert to ISO using `ccd2iso`, such as `ccd2iso mygame.img mygame.iso`. +For CD-based systems it's generally recommended to use CHD files (extension .chd) as this saves space due to compression +compared to BIN/CUE, IMG, ISO etc. The CHD format is also supported by most emulators. You can convert to CHD from +various formats using the MAME `chdman` utility, for example `chdman createcd -i mygame.iso -o mygame.chd`. Sometimes +chdman has issues converting from the IMG and BIN formats, and in this case it's possible to first convert to ISO +using `ccd2iso`, such as `ccd2iso mygame.img mygame.iso`. -MAME emulation is a bit special as the choice of emulator depends on which ROM set you're using. It's recommended to go for the latest available set, as MAME is constantly improved with more complete and accurate emulation. Therefore the `arcade` system is configured to use _MAME - Current_ by default, which as the name implies will be the latest available MAME version. But if you have a really slow computer you may want to use another ROM set such as the popular 0.78. In this case, you can either select _MAME 2003-Plus_ as an alternative emulator, or you can use the `mame` system which comes configured with this emulator as the default. There are more MAME versions available as alternative emulators, as you can see in the table below. +MAME emulation is a bit special as the choice of emulator depends on which ROM set you're using. It's recommended to go +for the latest available set, as MAME is constantly improved with more complete and accurate emulation. Therefore +the `arcade` system is configured to use _MAME - Current_ by default, which as the name implies will be the latest +available MAME version. But if you have a really slow computer you may want to use another ROM set such as the popular +0.78. In this case, you can either select _MAME 2003-Plus_ as an alternative emulator, or you can use the `mame` system +which comes configured with this emulator as the default. There are more MAME versions available as alternative +emulators, as you can see in the table below. -There are also other MAME forks and derivates available such as MAME4ALL, AdvanceMAME, FinalBurn Alpha and FinalBurn Neo but it's beyond the scope of this document to describe those in detail. For more information, refer to the [RetroPie arcade documentation](https://retropie.org.uk/docs/Arcade) which has a good overview of the various MAME alternatives. +There are also other MAME forks and derivates available such as MAME4ALL, AdvanceMAME, FinalBurn Alpha and FinalBurn Neo +but it's beyond the scope of this document to describe those in detail. For more information, refer to +the [RetroPie arcade documentation](https://retropie.org.uk/docs/Arcade) which has a good overview of the various MAME +alternatives. -In general .zip or .7z files are recommended for smaller-sized games like those from older systems (assuming the emulator supports it). But for CD-based systems it's not a good approach as uncompressing the larger CD images takes quite some time, leading to slow game launches. As explained above, converting CD images to CHD files is a better solution for achieving file compression while still enjoying fast game launches. +In general .zip or .7z files are recommended for smaller-sized games like those from older systems (assuming the +emulator supports it). But for CD-based systems it's not a good approach as uncompressing the larger CD images takes +quite some time, leading to slow game launches. As explained above, converting CD images to CHD files is a better +solution for achieving file compression while still enjoying fast game launches. Consider the table below a work in progress as it's obvioulsy not fully populated yet! @@ -1721,7 +1871,8 @@ All emulators are RetroArch cores unless marked as **(Standalone**) | gx4000 | Amstrad GX4000 | | | | | | intellivision | Mattel Electronics Intellivision | FreeIntv | | | | | kodi | Kodi home theatre software | N/A | | No | | -| lutris | Lutris open gaming platform | Lutris application **(Standalone)** [U] | | No | Shell script in root folder | +| lutris | Lutris open gaming platform | Lutris application **( +Standalone)** [U] | | No | Shell script in root folder | | lutro | Lutro game engine | Lutro | | | | | macintosh | Apple Macintosh | | | | | | mame | Multiple Arcade Machine Emulator | MAME 2003-Plus | MAME 2000,
MAME 2010,
MAME - Current,
FinalBurn Neo,
FB Alpha 2012 | Depends | Single archive file following MAME name standard in root folder | @@ -1783,11 +1934,13 @@ All emulators are RetroArch cores unless marked as **(Standalone**) | snesna | Nintendo SNES (Super Nintendo) [North America] | Snes9x - Current | Snes9x 2010,
bsnes,
bsnes-mercury Accuracy,
Beetle Supafaust [UW],
Mesen-S | No | Single archive or ROM file in root folder | | solarus | Solarus game engine | | | | | | spectravideo | Spectravideo | blueMSX | | | | -| steam | Valve Steam | Steam application **(Standalone)** | | No | Shell script/batch file in root folder | +| steam | Valve Steam | Steam application **( +Standalone)** | | No | Shell script/batch file in root folder | | stratagus | Stratagus game engine | | | | | | sufami | Bandai SuFami Turbo | Snes9x - Current | | | | | supergrafx | NEC SuperGrafx | Beetle SuperGrafx | Beetle PCE | | | -| switch | Nintendo Switch | Yuzu **(Standalone)** [UW] | | Yes | | +| switch | Nintendo Switch | Yuzu **( +Standalone)** [UW] | | Yes | | | tanodragon | Tano Dragon | | | | | | tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST | No | Single archive or ROM file in root folder | | tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST | Yes | | diff --git a/USERGUIDE.md b/USERGUIDE.md index 81eada3a2..0599b0741 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -364,7 +364,8 @@ The platform name for the Commodore 64 is `c64`, so the following structure woul ~/ROMs/c64/Multidisk/Pirates/Pirates!.m3u ``` -It's highly recommended to create `.m3u` playlist files for multi-disc images as this normally automates disk swapping in the emulator. It's then this .m3u file that should be selected for launching the game. +It's highly recommended to create `.m3u` playlist files for multi-disc images as this normally automates disk swapping +in the emulator. It's then this .m3u file that should be selected for launching the game. The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2.m3u: @@ -417,9 +418,13 @@ Apart from the potential difficulty in locating the emulator binary, there are s #### Commodore Amiga -There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play Amiga games as any console with game ROMs. +There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard +disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play +Amiga games as any console with game ROMs. -An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can either put single-disk images in the root folder or in a dedicated adf directory, or multiple-disk games in separate folders. It's highly recommended to create `.m3u` playlist files for multi-disc images as described earlier. +An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can +either put single-disk images in the root folder or in a dedicated adf directory, or multiple-disk games in separate +folders. It's highly recommended to create `.m3u` playlist files for multi-disc images as described earlier. Here's an example of what the file structure could look like: @@ -859,7 +864,8 @@ If this setting is enabled and a folder has its flag set to be excluded from the **Scrape actual folders** _(Multi-scraper only)_ -Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any multi-disc games where there is a folder for each individual game. +Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any +multi-disc games where there is a folder for each individual game. **Auto-retry on peer verification errors** _(ScreenScraper only)_ @@ -1206,7 +1212,11 @@ If this option is disabled, hidden files and folders within the ROMs directory t **Show hidden games (requires restart)** -You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want to see some batch files and executables inside ES-DE, or for multi-disc games where you may only want to show the .m3u playlists and not the individual game files. By disabling this option these files will not be processed at all when ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in the gamelist view to visually indicate that they are hidden. +You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want +to see some batch files and executables inside ES-DE, or for multi-disc games where you may only want to show the .m3u +playlists and not the individual game files. By disabling this option these files will not be processed at all when +ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in +the gamelist view to visually indicate that they are hidden. **Enable custom event scripts** @@ -1394,7 +1404,11 @@ A flag to mark whether the game is suitable for children. This will be applied a **Hidden** -A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game files for multi-disc games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden entry. +A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not +be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game +files for multi-disc games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games +has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden +entry. **Broken/not working** @@ -1402,15 +1416,27 @@ A flag to indicate whether the game is broken. Useful for MAME games for instanc **Exclude from game counter** _(files only)_ -A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be included in the game counter shown per system on the system view, and it will not be included in the system information field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite useful for multi-file games such as multi-disc Amiga or Commodore 64 games, or for DOS games where you want to exclude setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this flag set will have a lower opacity in the gamelists, making them easy to spot. +A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be +included in the game counter shown per system on the system view, and it will not be included in the system information +field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite +useful for multi-file games such as multi-disc Amiga or Commodore 64 games, or for DOS games where you want to exclude +setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this +flag set will have a lower opacity in the gamelists, making them easy to spot. **Exclude from multi-scraper** -Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for multi-disc games for example. There is an option in the scraper settings to ignore this flag, but by default the multi-scraper will respect it. +Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for +multi-disc games for example. There is an option in the scraper settings to ignore this flag, but by default the +multi-scraper will respect it. **Hide metadata fields** -This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. +This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for +situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or +INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u +playlist should have any metadata values. The only fields shown with this option enabled are the game name and +description. Using the description it's possible to write some comments regarding the file or folder, should you want +to. It's also possible to display game images and videos with this setting enabled. **Launch command** _(files only)_ diff --git a/es-app/CMakeLists.txt b/es-app/CMakeLists.txt index 9f4dbe7e1..9dbb9681e 100644 --- a/es-app/CMakeLists.txt +++ b/es-app/CMakeLists.txt @@ -10,106 +10,106 @@ project("emulationstation-de") set(ES_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h - # GUIs - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h + # GUIs + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h - # Scrapers - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.h + # Scrapers + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.h - # Views - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h -) + # Views + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h + ) set(ES_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp - # GUIs - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp + # GUIs + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp - # Scrapers - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.cpp + # Scrapers + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.cpp - # Views - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp -) + # Views + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp + ) if(WIN32) LIST(APPEND ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation.rc) @@ -134,55 +134,55 @@ endif() # Setup for installation and package generation. if(WIN32) install(TARGETS EmulationStation RUNTIME DESTINATION .) - if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") install(FILES ../avcodec-58.dll - ../avfilter-7.dll - ../avformat-58.dll - ../avutil-56.dll - ../postproc-55.dll - ../swresample-3.dll - ../swscale-5.dll - ../FreeImage.dll - ../freetype.dll - ../glew32.dll - ../libcrypto-1_1-x64.dll - ../libcurl-x64.dll - ../libssl-1_1-x64.dll - ../MSVCP140.dll - ../pugixml.dll - ../SDL2.dll - ../VCOMP140.DLL - ../VCRUNTIME140.dll - ../VCRUNTIME140_1.dll - DESTINATION .) - if(VLC_PLAYER) + ../avfilter-7.dll + ../avformat-58.dll + ../avutil-56.dll + ../postproc-55.dll + ../swresample-3.dll + ../swscale-5.dll + ../FreeImage.dll + ../freetype.dll + ../glew32.dll + ../libcrypto-1_1-x64.dll + ../libcurl-x64.dll + ../libssl-1_1-x64.dll + ../MSVCP140.dll + ../pugixml.dll + ../SDL2.dll + ../VCOMP140.DLL + ../VCRUNTIME140.dll + ../VCRUNTIME140_1.dll + DESTINATION .) + if (VLC_PLAYER) install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .) - endif() - else() + endif () + else () install(FILES ../avcodec-58.dll - ../avfilter-7.dll - ../avformat-58.dll - ../avutil-56.dll - ../postproc-55.dll - ../swresample-3.dll - ../swscale-5.dll - ../FreeImage.dll - ../glew32.dll - ../libcrypto-1_1-x64.dll - ../libcurl-x64.dll - ../libfreetype.dll - ../libpugixml.dll - ../libssl-1_1-x64.dll - ../SDL2.dll - ../vcomp140.dll - DESTINATION .) - if(VLC_PLAYER) + ../avfilter-7.dll + ../avformat-58.dll + ../avutil-56.dll + ../postproc-55.dll + ../swresample-3.dll + ../swscale-5.dll + ../FreeImage.dll + ../glew32.dll + ../libcrypto-1_1-x64.dll + ../libcurl-x64.dll + ../libfreetype.dll + ../libpugixml.dll + ../libssl-1_1-x64.dll + ../SDL2.dll + ../vcomp140.dll + DESTINATION .) + if (VLC_PLAYER) install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .) - endif() - endif() - if(VLC_PLAYER) + endif () + endif () + if (VLC_PLAYER) install(DIRECTORY ${CMAKE_SOURCE_DIR}/plugins DESTINATION .) - endif() + endif () install(FILES ../LICENSE DESTINATION .) install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION .) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes DESTINATION .) @@ -206,81 +206,81 @@ elseif(APPLE) # on your system (e.g. if using libSDL2-2.1.0.dylib instead of libSDL2-2.0.0.dylib). # This problem definitely needs to be resolved properly at a later date. add_custom_command(TARGET EmulationStation POST_BUILD COMMAND ${CMAKE_INSTALL_NAME_TOOL} - -change /usr/local/lib/libavcodec.58.dylib @rpath/libavcodec.58.dylib - -change /usr/local/lib/libavfilter.7.dylib @rpath/libavfilter.7.dylib - -change /usr/local/lib/libavformat.58.dylib @rpath/libavformat.58.dylib - -change /usr/local/lib/libavutil.56.dylib @rpath/libavutil.56.dylib - -change /usr/local/lib/libswresample.3.dylib @rpath/libswresample.3.dylib - -change /usr/local/lib/libswscale.5.dylib @rpath/libswscale.5.dylib - -change /usr/local/opt/freeimage/lib/libfreeimage.dylib @rpath/libfreeimage.dylib - -change /usr/local/opt/freetype/lib/libfreetype.6.dylib @rpath/libfreetype.6.dylib - -change /usr/local/opt/libpng/lib/libpng16.16.dylib @rpath/libpng16.16.dylib - -change /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib @rpath/libSDL2-2.0.0.dylib - $) + -change /usr/local/lib/libavcodec.58.dylib @rpath/libavcodec.58.dylib + -change /usr/local/lib/libavfilter.7.dylib @rpath/libavfilter.7.dylib + -change /usr/local/lib/libavformat.58.dylib @rpath/libavformat.58.dylib + -change /usr/local/lib/libavutil.56.dylib @rpath/libavutil.56.dylib + -change /usr/local/lib/libswresample.3.dylib @rpath/libswresample.3.dylib + -change /usr/local/lib/libswscale.5.dylib @rpath/libswscale.5.dylib + -change /usr/local/opt/freeimage/lib/libfreeimage.dylib @rpath/libfreeimage.dylib + -change /usr/local/opt/freetype/lib/libfreetype.6.dylib @rpath/libfreetype.6.dylib + -change /usr/local/opt/libpng/lib/libpng16.16.dylib @rpath/libpng16.16.dylib + -change /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib @rpath/libSDL2-2.0.0.dylib + $) set(APPLE_DYLIB_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE) + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) install(FILES ${CMAKE_SOURCE_DIR}/libavcodec.58.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libavfilter.7.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libavformat.58.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libavutil.56.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libpostproc.55.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libswresample.3.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libswscale.5.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libfdk-aac.2.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libfreeimage.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libfreetype.6.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libpng16.16.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libSDL2-2.0.0.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) - if(VLC_PLAYER) + if (VLC_PLAYER) install(FILES ${CMAKE_SOURCE_DIR}/libvlc.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libvlccore.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(DIRECTORY ${CMAKE_SOURCE_DIR}/plugins - DESTINATION ../MacOS) - endif() + DESTINATION ../MacOS) + endif () install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ../Resources) install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources DESTINATION ../Resources) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes DESTINATION ../Resources) install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION ../Resources) -else() +else () install(TARGETS emulationstation RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) - if(CMAKE_SYSTEM_NAME MATCHES "Linux") + if (CMAKE_SYSTEM_NAME MATCHES "Linux") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6) - else() + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6) + else () install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz - DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man6) - endif() + DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man6) + endif () install(FILES ${CMAKE_SOURCE_DIR}/LICENSE - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.desktop - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.svg - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pixmaps) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pixmaps) install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) endif() include(InstallRequiredSystemLibraries) @@ -317,13 +317,13 @@ endif() # Settings per operating system and generator type. if(APPLE) set(CPACK_GENERATOR "Bundle") - if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) + if (CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) set(CPACK_PACKAGE_FILE_NAME "EmulationStation-DE-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}_legacy") set(CPACK_DMG_VOLUME_NAME "EmulationStation Desktop Edition ${CPACK_PACKAGE_VERSION}_legacy") - else() + else () set(CPACK_PACKAGE_FILE_NAME "EmulationStation-DE-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}") set(CPACK_DMG_VOLUME_NAME "EmulationStation Desktop Edition ${CPACK_PACKAGE_VERSION}") - endif() + endif () set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE.icns") set(CPACK_DMG_DS_STORE "${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE_DS_Store") set(CPACK_BUNDLE_NAME "EmulationStation Desktop Edition") @@ -331,9 +331,9 @@ if(APPLE) set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE_Info.plist") if(MACOS_CODESIGN_IDENTITY) set(CPACK_BUNDLE_APPLE_CERT_APP "Developer ID Application: ${MACOS_CODESIGN_IDENTITY}") - if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER 10.13) + if (CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER 10.13) set(CPACK_BUNDLE_APPLE_CODESIGN_PARAMETER "--deep --force --options runtime") - endif() + endif () endif() elseif(WIN32) set(CPACK_GENERATOR "NSIS") @@ -354,9 +354,9 @@ elseif(WIN32) else() set(CPACK_PACKAGE_INSTALL_DIRECTORY "emulationstation_${CMAKE_PACKAGE_VERSION}") set(CPACK_PACKAGE_EXECUTABLES "emulationstation" "emulationstation") - if(LINUX_CPACK_GENERATOR STREQUAL "DEB") + if (LINUX_CPACK_GENERATOR STREQUAL "DEB") set(CPACK_GENERATOR "DEB") - endif() + endif () set(CPACK_DEBIAN_FILE_NAME "emulationstation-de-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.deb") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Leon Styhre ") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://es-de.org") @@ -366,9 +366,9 @@ else() set(CPACK_DEBIAN_PACKAGE_DEPENDS "vlc") endif() set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) - if(LINUX_CPACK_GENERATOR STREQUAL "RPM") + if (LINUX_CPACK_GENERATOR STREQUAL "RPM") set(CPACK_GENERATOR "RPM") - endif() + endif () set(CPACK_RPM_FILE_NAME "emulationstation-de-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.rpm") set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) set(CPACK_RPM_PACKAGE_LICENSE "MIT") diff --git a/es-app/src/CollectionSystemsManager.cpp b/es-app/src/CollectionSystemsManager.cpp index 282b880a2..f4b7823d4 100644 --- a/es-app/src/CollectionSystemsManager.cpp +++ b/es-app/src/CollectionSystemsManager.cpp @@ -377,21 +377,21 @@ void CollectionSystemsManager::updateCollectionSystem(FileData* file, Collection // If the countasgame flag has been set to false, then remove the game. if (curSys->isGroupedCustomCollection()) { ViewController::get() - ->getGameListView(curSys->getRootFolder()->getParent()->getSystem()) - .get() - ->remove(collectionEntry, false); - FileData* parentRootFolder = - rootFolder->getParent()->getSystem()->getRootFolder(); + ->getGameListView(curSys->getRootFolder()->getParent()->getSystem()) + .get() + ->remove(collectionEntry, false); + FileData *parentRootFolder = + rootFolder->getParent()->getSystem()->getRootFolder(); parentRootFolder->sort(parentRootFolder->getSortTypeFromString( - parentRootFolder->getSortTypeString()), + parentRootFolder->getSortTypeString()), mFavoritesSorting); - GuiInfoPopup* s = new GuiInfoPopup( - mWindow, - "DISABLED '" + + GuiInfoPopup *s = new GuiInfoPopup( + mWindow, + "DISABLED '" + Utils::String::toUpper( - Utils::String::removeParenthesis(file->getName())) + + Utils::String::removeParenthesis(file->getName())) + "' IN '" + Utils::String::toUpper(sysData.system->getName()) + "'", - 4000); + 4000); mWindow->setInfoPopup(s); } else { @@ -550,28 +550,26 @@ bool CollectionSystemsManager::isThemeCustomCollectionCompatible( return true; } -std::string CollectionSystemsManager::getValidNewCollectionName(std::string inName, int index) -{ +std::string CollectionSystemsManager::getValidNewCollectionName(std::string inName, int index) { std::string name = inName; // Trim leading and trailing whitespaces. name.erase(name.begin(), std::find_if(name.begin(), name.end(), [](char c) { - return !std::isspace(static_cast(c)); - })); + return !std::isspace(static_cast(c)); + })); name.erase(std::find_if(name.rbegin(), name.rend(), [](char c) { return !std::isspace(static_cast(c)); }) - .base(), + .base(), name.end()); if (index == 0) { size_t remove = std::string::npos; // Get valid name. while ((remove = name.find_first_not_of( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-[]()' ")) != + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-[]()' ")) != std::string::npos) name.erase(remove, 1); - } - else { + } else { name += " (" + std::to_string(index) + ")"; } @@ -1333,8 +1331,7 @@ void CollectionSystemsManager::addEnabledCollectionsToDisplayedSystems( } } -std::vector CollectionSystemsManager::getSystemsFromConfig() -{ +std::vector CollectionSystemsManager::getSystemsFromConfig() { std::vector systems; std::vector configPaths = SystemData::getConfigPath(false); @@ -1342,7 +1339,7 @@ std::vector CollectionSystemsManager::getSystemsFromConfig() // file under ~/.emulationstation/custom_systems as we really want to include all the themes // supported by ES-DE. Otherwise a user may accidentally create a custom collection that // corresponds to a supported theme. - for (auto path : configPaths) { + for (auto path: configPaths) { if (!Utils::FileSystem::exists(path)) return systems; diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 8b441da80..c3fdf9103 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -30,18 +30,11 @@ #include FileData::FileData(FileType type, - const std::string& path, - SystemEnvironmentData* envData, - SystemData* system) - : metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) - , mSourceFileData(nullptr) - , mParent(nullptr) - , mType(type) - , mPath(path) - , mEnvData(envData) - , mSystem(system) - , mOnlyFolders(false) - , mDeletionFlag(false) + const std::string &path, + SystemEnvironmentData *envData, + SystemData *system) + : metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA), mSourceFileData(nullptr), mParent(nullptr), + mType(type), mPath(path), mEnvData(envData), mSystem(system), mOnlyFolders(false), mDeletionFlag(false) { // Metadata needs at least a name field (since that's what getName() will return). if (metadata.get("name").empty()) { @@ -742,11 +735,10 @@ FileData::SortType FileData::getSortTypeFromString(std::string desc) return FileSorts::SortTypes.at(0); } -void FileData::launchGame(Window* window) -{ +void FileData::launchGame(Window *window) { LOG(LogInfo) << "Launching game \"" << this->metadata.get("name") << "\"..."; - SystemData* gameSystem = nullptr; + SystemData *gameSystem = nullptr; std::string command = ""; std::string alternativeEmulator; @@ -1355,8 +1347,8 @@ const std::string& CollectionFileData::getName() if (mDirty) { mCollectionFileName = mSourceFileData->metadata.get("name"); mCollectionFileName.append(" [") - .append(Utils::String::toUpper(mSourceFileData->getSystem()->getName())) - .append("]"); + .append(Utils::String::toUpper(mSourceFileData->getSystem()->getName())) + .append("]"); mDirty = false; } diff --git a/es-app/src/FileFilterIndex.cpp b/es-app/src/FileFilterIndex.cpp index 75daf387f..4e464b020 100644 --- a/es-app/src/FileFilterIndex.cpp +++ b/es-app/src/FileFilterIndex.cpp @@ -20,23 +20,15 @@ #define INCLUDE_UNKNOWN false; FileFilterIndex::FileFilterIndex() - : mFilterByText(false) - , mTextRemoveSystem(false) - , mFilterByFavorites(false) - , mFilterByGenre(false) - , mFilterByPlayers(false) - , mFilterByPubDev(false) - , mFilterByRatings(false) - , mFilterByKidGame(false) - , mFilterByCompleted(false) - , mFilterByBroken(false) - , mFilterByHidden(false) + : mFilterByText(false), mTextRemoveSystem(false), mFilterByFavorites(false), mFilterByGenre(false), + mFilterByPlayers(false), mFilterByPubDev(false), mFilterByRatings(false), mFilterByKidGame(false), + mFilterByCompleted(false), mFilterByBroken(false), mFilterByHidden(false) { clearAllFilters(); // clang-format off FilterDataDecl filterDecls[] = { - //type //allKeys //filteredBy //filteredKeys //primaryKey //hasSecondaryKey //secondaryKey //menuLabel + //type //allKeys //filteredBy //filteredKeys //primaryKey //hasSecondaryKey //secondaryKey //menuLabel {FAVORITES_FILTER, &mFavoritesIndexAllKeys, &mFilterByFavorites, &mFavoritesIndexFilteredKeys, "favorite", false, "", "FAVORITES"}, {GENRE_FILTER, &mGenreIndexAllKeys, &mFilterByGenre, &mGenreIndexFilteredKeys, "genre", true, "genre", "GENRE"}, {PLAYER_FILTER, &mPlayersIndexAllKeys, &mFilterByPlayers, &mPlayersIndexFilteredKeys, "players", false, "", "PLAYERS"}, @@ -365,11 +357,10 @@ bool FileFilterIndex::showFile(FileData* game) // in [] from the search string. if (mTextFilter != "" && mTextRemoveSystem && !(Utils::String::toUpper(game->getName().substr(0, game->getName().find_last_of("["))) - .find(mTextFilter) != std::string::npos)) { + .find(mTextFilter) != std::string::npos)) { return false; - } - else if (mTextFilter != "" && - !(Utils::String::toUpper(game->getName()).find(mTextFilter) != std::string::npos)) { + } else if (mTextFilter != "" && + !(Utils::String::toUpper(game->getName()).find(mTextFilter) != std::string::npos)) { return false; } @@ -381,8 +372,7 @@ bool FileFilterIndex::showFile(FileData* game) FilterDataDecl filterData = (*it); if (filterData.primaryKey == "kidgame" && UIModeController::getInstance()->isUIModeKid()) { return (getIndexableKey(game, filterData.type, false) != "FALSE"); - } - else if (*(filterData.filteredByRef)) { + } else if (*(filterData.filteredByRef)) { // Try to find a match. std::string key = getIndexableKey(game, filterData.type, false); keepGoing = isKeyBeingFilteredBy(key, filterData.type); diff --git a/es-app/src/FileFilterIndex.h b/es-app/src/FileFilterIndex.h index 18cbf4469..3e2af831e 100644 --- a/es-app/src/FileFilterIndex.h +++ b/es-app/src/FileFilterIndex.h @@ -48,22 +48,37 @@ class FileFilterIndex public: FileFilterIndex(); ~FileFilterIndex(); - void addToIndex(FileData* game); - void removeFromIndex(FileData* game); - void setFilter(FilterIndexType type, std::vector* values); + + void addToIndex(FileData *game); + + void removeFromIndex(FileData *game); + + void setFilter(FilterIndexType type, std::vector *values); + void setTextFilter(std::string textFilter); + std::string getTextFilter() { return mTextFilter; } + void clearAllFilters(); + void debugPrintIndexes(); - bool showFile(FileData* game); + + bool showFile(FileData *game); + bool isFiltered(); + bool isKeyBeingFilteredBy(std::string key, FilterIndexType type); - std::vector& getFilterDataDecls() { return filterDataDecl; } + + std::vector &getFilterDataDecls() { return filterDataDecl; } + void setTextRemoveSystem(bool status) { mTextRemoveSystem = status; } - void importIndex(FileFilterIndex* indexToImport); + void importIndex(FileFilterIndex *indexToImport); + void resetIndex(); + void resetFilters(); + void setKidModeFilters(); private: diff --git a/es-app/src/FileSorts.cpp b/es-app/src/FileSorts.cpp index 916ecd819..196244391 100644 --- a/es-app/src/FileSorts.cpp +++ b/es-app/src/FileSorts.cpp @@ -227,8 +227,7 @@ namespace FileSorts return system1.compare(system2) < 0; } - bool compareSystemDescending(const FileData* file1, const FileData* file2) - { + bool compareSystemDescending(const FileData *file1, const FileData *file2) { std::string system1 = Utils::String::toUpper(file1->getSystemName()); std::string system2 = Utils::String::toUpper(file2->getSystemName()); return system1.compare(system2) > 0; diff --git a/es-app/src/FileSorts.h b/es-app/src/FileSorts.h index e7f84346d..950878c27 100644 --- a/es-app/src/FileSorts.h +++ b/es-app/src/FileSorts.h @@ -18,24 +18,42 @@ namespace FileSorts { bool compareName(const FileData* file1, const FileData* file2); bool compareNameDescending(const FileData* file1, const FileData* file2); - bool compareRating(const FileData* file1, const FileData* file2); - bool compareRatingDescending(const FileData* file1, const FileData* file2); - bool compareReleaseDate(const FileData* file1, const FileData* file2); - bool compareReleaseDateDescending(const FileData* file1, const FileData* file2); - bool compareDeveloper(const FileData* file1, const FileData* file2); - bool compareDeveloperDescending(const FileData* file1, const FileData* file2); - bool comparePublisher(const FileData* file1, const FileData* file2); - bool comparePublisherDescending(const FileData* file1, const FileData* file2); - bool compareGenre(const FileData* file1, const FileData* file2); - bool compareGenreDescending(const FileData* file1, const FileData* file2); - bool compareNumPlayers(const FileData* file1, const FileData* file2); - bool compareNumPlayersDescending(const FileData* file1, const FileData* file2); - bool compareLastPlayed(const FileData* file1, const FileData* file2); - bool compareLastPlayedDescending(const FileData* file1, const FileData* file2); - bool compareTimesPlayed(const FileData* file1, const FileData* fil2); - bool compareTimesPlayedDescending(const FileData* file1, const FileData* fil2); - bool compareSystem(const FileData* file1, const FileData* file2); - bool compareSystemDescending(const FileData* file1, const FileData* file2); + + bool compareRating(const FileData *file1, const FileData *file2); + + bool compareRatingDescending(const FileData *file1, const FileData *file2); + + bool compareReleaseDate(const FileData *file1, const FileData *file2); + + bool compareReleaseDateDescending(const FileData *file1, const FileData *file2); + + bool compareDeveloper(const FileData *file1, const FileData *file2); + + bool compareDeveloperDescending(const FileData *file1, const FileData *file2); + + bool comparePublisher(const FileData *file1, const FileData *file2); + + bool comparePublisherDescending(const FileData *file1, const FileData *file2); + + bool compareGenre(const FileData *file1, const FileData *file2); + + bool compareGenreDescending(const FileData *file1, const FileData *file2); + + bool compareNumPlayers(const FileData *file1, const FileData *file2); + + bool compareNumPlayersDescending(const FileData *file1, const FileData *file2); + + bool compareLastPlayed(const FileData *file1, const FileData *file2); + + bool compareLastPlayedDescending(const FileData *file1, const FileData *file2); + + bool compareTimesPlayed(const FileData *file1, const FileData *fil2); + + bool compareTimesPlayedDescending(const FileData *file1, const FileData *fil2); + + bool compareSystem(const FileData *file1, const FileData *file2); + + bool compareSystemDescending(const FileData *file1, const FileData *file2); extern const std::vector SortTypes; } // namespace FileSorts diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp index 59cc0052e..cf4298c2d 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -200,12 +200,12 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st glm::vec2{0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); row.addElement(newCollection, true); row.addElement(bracketNewCollection, false); - auto createCollectionCall = [this](const std::string& newVal) { + auto createCollectionCall = [this](const std::string &newVal) { std::string name = newVal; // We need to store the first GUI and remove it, as it'll be deleted // by the actual GUI. - Window* window = mWindow; - GuiComponent* topGui = window->peekGui(); + Window *window = mWindow; + GuiComponent *topGui = window->peekGui(); window->removeGui(topGui); createCustomCollection(name); }; @@ -213,11 +213,10 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st if (Settings::getInstance()->getBool("VirtualKeyboard")) { row.makeAcceptInputHandler([this, createCollectionCall] { mWindow->pushGui(new GuiTextEditKeyboardPopup( - mWindow, getHelpStyle(), "New Collection Name", "", createCollectionCall, false, - "CREATE", "CREATE COLLECTION?")); + mWindow, getHelpStyle(), "New Collection Name", "", createCollectionCall, false, + "CREATE", "CREATE COLLECTION?")); }); - } - else { + } else { row.makeAcceptInputHandler([this, createCollectionCall] { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "New Collection Name", "", createCollectionCall, false, "CREATE", @@ -229,11 +228,11 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st // Delete custom collection. row.elements.clear(); auto deleteCollection = std::make_shared( - mWindow, "DELETE CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + mWindow, "DELETE CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); auto bracketDeleteCollection = std::make_shared(mWindow); bracketDeleteCollection->setImage(":/graphics/arrow.svg"); bracketDeleteCollection->setResize( - glm::vec2{0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); + glm::vec2{0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); row.addElement(deleteCollection, true); row.addElement(bracketDeleteCollection, false); row.makeAcceptInputHandler([this, customSystems] { diff --git a/es-app/src/guis/GuiGameScraper.cpp b/es-app/src/guis/GuiGameScraper.cpp index 9ddc85caa..3f65554fa 100644 --- a/es-app/src/guis/GuiGameScraper.cpp +++ b/es-app/src/guis/GuiGameScraper.cpp @@ -18,14 +18,11 @@ #include "components/TextComponent.h" #include "views/ViewController.h" -GuiGameScraper::GuiGameScraper(Window* window, +GuiGameScraper::GuiGameScraper(Window *window, ScraperSearchParams params, - std::function doneFunc) - : GuiComponent(window) - , mClose(false) - , mGrid(window, glm::ivec2{1, 7}) - , mBox(window, ":/graphics/frame.svg") - , mSearchParams(params) + std::function doneFunc) + : GuiComponent(window), mClose(false), mGrid(window, glm::ivec2{1, 7}), mBox(window, ":/graphics/frame.svg"), + mSearchParams(params) { addChild(&mBox); addChild(&mGrid); diff --git a/es-app/src/guis/GuiGamelistFilter.cpp b/es-app/src/guis/GuiGamelistFilter.cpp index 679e785cb..8330b4424 100644 --- a/es-app/src/guis/GuiGamelistFilter.cpp +++ b/es-app/src/guis/GuiGamelistFilter.cpp @@ -29,8 +29,7 @@ GuiGamelistFilter::GuiGamelistFilter(Window* window, initializeMenu(); } -void GuiGamelistFilter::initializeMenu() -{ +void GuiGamelistFilter::initializeMenu() { addChild(&mMenu); // Get filters from system. @@ -94,13 +93,12 @@ void GuiGamelistFilter::resetAllFilters() GuiGamelistFilter::~GuiGamelistFilter() { mFilterOptions.clear(); } -void GuiGamelistFilter::addFiltersToMenu() -{ +void GuiGamelistFilter::addFiltersToMenu() { ComponentListRow row; auto lbl = std::make_shared( - mWindow, Utils::String::toUpper(ViewController::KEYBOARD_CHAR + " GAME NAME"), - Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + mWindow, Utils::String::toUpper(ViewController::KEYBOARD_CHAR + " GAME NAME"), + Font::get(FONT_SIZE_MEDIUM), 0x777777FF); mTextFilterField = std::make_shared(mWindow, "", Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT); @@ -123,7 +121,7 @@ void GuiGamelistFilter::addFiltersToMenu() } // Callback function. - auto updateVal = [this](const std::string& newVal) { + auto updateVal = [this](const std::string &newVal) { mTextFilterField->setValue(Utils::String::toUpper(newVal)); mFilterIndex->setTextFilter(Utils::String::toUpper(newVal)); }; @@ -134,8 +132,7 @@ void GuiGamelistFilter::addFiltersToMenu() mTextFilterField->getValue(), updateVal, false, "OK", "APPLY CHANGES?")); }); - } - else { + } else { row.makeAcceptInputHandler([this, updateVal] { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "GAME NAME", mTextFilterField->getValue(), updateVal, false, diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index b7cbbd1cb..c63e38da0 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -25,15 +25,9 @@ #include "views/ViewController.h" #include "views/gamelist/IGameListView.h" -GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) - : GuiComponent(window) - , mMenu(window, "OPTIONS") - , mSystem(system) - , mFiltersChanged(false) - , mCancelled(false) - , mIsCustomCollection(false) - , mIsCustomCollectionGroup(false) - , mCustomCollectionSystem(nullptr) +GuiGamelistOptions::GuiGamelistOptions(Window *window, SystemData *system) + : GuiComponent(window), mMenu(window, "OPTIONS"), mSystem(system), mFiltersChanged(false), mCancelled(false), + mIsCustomCollection(false), mIsCustomCollectionGroup(false), mCustomCollectionSystem(nullptr) { addChild(&mMenu); diff --git a/es-app/src/guis/GuiLaunchScreen.cpp b/es-app/src/guis/GuiLaunchScreen.cpp index bffcefb71..ce347b85f 100644 --- a/es-app/src/guis/GuiLaunchScreen.cpp +++ b/es-app/src/guis/GuiLaunchScreen.cpp @@ -14,12 +14,9 @@ #include "components/TextComponent.h" #include "utils/StringUtil.h" -GuiLaunchScreen::GuiLaunchScreen(Window* window) - : GuiComponent(window) - , mWindow(window) - , mBackground(window, ":/graphics/frame.svg") - , mGrid(nullptr) - , mMarquee(nullptr) +GuiLaunchScreen::GuiLaunchScreen(Window *window) + : GuiComponent(window), mWindow(window), mBackground(window, ":/graphics/frame.svg"), mGrid(nullptr), + mMarquee(nullptr) { addChild(&mBackground); mWindow->setLaunchScreen(this); @@ -220,8 +217,7 @@ void GuiLaunchScreen::update(int deltaTime) mScaleUp = glm::clamp(mScaleUp + 0.07f, 0.0f, 1.0f); } -void GuiLaunchScreen::render(const glm::mat4& /*parentTrans*/) -{ +void GuiLaunchScreen::render(const glm::mat4 & /*parentTrans*/) { // Scale up animation. if (mScaleUp < 1.0f) setScale(mScaleUp); diff --git a/es-app/src/guis/GuiLaunchScreen.h b/es-app/src/guis/GuiLaunchScreen.h index c40d3c220..352bfa711 100644 --- a/es-app/src/guis/GuiLaunchScreen.h +++ b/es-app/src/guis/GuiLaunchScreen.h @@ -21,21 +21,24 @@ class FileData; class GuiLaunchScreen : public Window::GuiLaunchScreen, GuiComponent { public: - GuiLaunchScreen(Window* window); + GuiLaunchScreen(Window *window); + virtual ~GuiLaunchScreen(); - virtual void displayLaunchScreen(FileData* game) override; + virtual void displayLaunchScreen(FileData *game) override; + virtual void closeLaunchScreen() override; void onSizeChanged() override; virtual void update(int deltaTime) override; - virtual void render(const glm::mat4& parentTrans) override; + + virtual void render(const glm::mat4 &parentTrans) override; private: - Window* mWindow; + Window *mWindow; NinePatchComponent mBackground; - ComponentGrid* mGrid; + ComponentGrid *mGrid; std::shared_ptr mTitle; std::shared_ptr mGameName; diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 63090620b..23b17a8eb 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -36,10 +36,8 @@ #include #include -GuiMenu::GuiMenu(Window* window) - : GuiComponent(window) - , mMenu(window, "MAIN MENU") - , mVersion(window) +GuiMenu::GuiMenu(Window *window) + : GuiComponent(window), mMenu(window, "MAIN MENU"), mVersion(window) { bool isFullUI = UIModeController::getInstance()->isUIModeFull(); @@ -824,17 +822,16 @@ void GuiMenu::openOtherOptions() multiLineMediaDir] { if (Settings::getInstance()->getBool("VirtualKeyboard")) { mWindow->pushGui(new GuiTextEditKeyboardPopup( - mWindow, getHelpStyle(), titleMediaDir, - Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, - multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, - defaultDirectoryText, "load default directory")); - } - else { + mWindow, getHelpStyle(), titleMediaDir, + Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, + multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, + defaultDirectoryText, "load default directory")); + } else { mWindow->pushGui(new GuiTextEditPopup( - mWindow, getHelpStyle(), titleMediaDir, - Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, - multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, - defaultDirectoryText, "load default directory")); + mWindow, getHelpStyle(), titleMediaDir, + Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, + multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, + defaultDirectoryText, "load default directory")); } }); s->addRow(rowMediaDir); diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 1c73698ab..2aa7a5213 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -31,25 +31,18 @@ #include "utils/StringUtil.h" #include "views/ViewController.h" -GuiMetaDataEd::GuiMetaDataEd(Window* window, - MetaDataList* md, - const std::vector& mdd, +GuiMetaDataEd::GuiMetaDataEd(Window *window, + MetaDataList *md, + const std::vector &mdd, ScraperSearchParams scraperParams, - const std::string& /*header*/, + const std::string & /*header*/, std::function saveCallback, std::function clearGameFunc, std::function deleteGameFunc) - : GuiComponent(window) - , mBackground(window, ":/graphics/frame.svg") - , mGrid(window, glm::ivec2{1, 3}) - , mScraperParams(scraperParams) - , mMetaDataDecl(mdd) - , mMetaData(md) - , mSavedCallback(saveCallback) - , mClearGameFunc(clearGameFunc) - , mDeleteGameFunc(deleteGameFunc) - , mMediaFilesUpdated(false) - , mInvalidEmulatorEntry(false) + : GuiComponent(window), mBackground(window, ":/graphics/frame.svg"), mGrid(window, glm::ivec2{1, 3}), + mScraperParams(scraperParams), mMetaDataDecl(mdd), mMetaData(md), mSavedCallback(saveCallback), + mClearGameFunc(clearGameFunc), mDeleteGameFunc(deleteGameFunc), mMediaFilesUpdated(false), + mInvalidEmulatorEntry(false) { addChild(&mBackground); addChild(&mGrid); @@ -218,11 +211,11 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, if (mInvalidEmulatorEntry || scraperParams.system->getSystemEnvData()->mLaunchCommands.size() > 1) { row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal, - originalValue] { - GuiSettings* s = nullptr; + originalValue] { + GuiSettings *s = nullptr; bool singleEntry = - scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1; + scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1; if (mInvalidEmulatorEntry && singleEntry) s = new GuiSettings(mWindow, "CLEAR INVALID ENTRY"); @@ -233,16 +226,16 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, return; std::vector> launchCommands = - scraperParams.system->getSystemEnvData()->mLaunchCommands; + scraperParams.system->getSystemEnvData()->mLaunchCommands; if (ed->getValue() != "" && mInvalidEmulatorEntry && singleEntry) launchCommands.push_back(std::make_pair( - "", ViewController::EXCLAMATION_CHAR + " " + originalValue)); + "", ViewController::EXCLAMATION_CHAR + " " + originalValue)); else if (ed->getValue() != "") launchCommands.push_back(std::make_pair( - "", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY")); + "", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY")); - for (auto entry : launchCommands) { + for (auto entry: launchCommands) { std::string selectedLabel = ed->getValue(); std::string label; ComponentListRow row; @@ -363,8 +356,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, ed->setColor(DEFAULT_TEXTCOLOR); else ed->setColor(TEXTCOLOR_USERMARKED); - } - else { + } else { ed->setValue(newVal); if (newVal == originalValue) ed->setColor(DEFAULT_TEXTCOLOR); @@ -376,11 +368,10 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, if (Settings::getInstance()->getBool("VirtualKeyboard")) { row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { mWindow->pushGui(new GuiTextEditKeyboardPopup( - mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine, - "apply", "APPLY CHANGES?", "", "")); + mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine, + "apply", "APPLY CHANGES?", "", "")); }); - } - else { + } else { row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine, diff --git a/es-app/src/guis/GuiOfflineGenerator.cpp b/es-app/src/guis/GuiOfflineGenerator.cpp index d88e67932..2f214f987 100644 --- a/es-app/src/guis/GuiOfflineGenerator.cpp +++ b/es-app/src/guis/GuiOfflineGenerator.cpp @@ -13,11 +13,9 @@ #include "components/MenuComponent.h" #include "views/ViewController.h" -GuiOfflineGenerator::GuiOfflineGenerator(Window* window, const std::queue& gameQueue) - : GuiComponent(window) - , mGameQueue(gameQueue) - , mBackground(window, ":/graphics/frame.svg") - , mGrid(window, glm::ivec2{6, 13}) +GuiOfflineGenerator::GuiOfflineGenerator(Window *window, const std::queue &gameQueue) + : GuiComponent(window), mGameQueue(gameQueue), mBackground(window, ":/graphics/frame.svg"), + mGrid(window, glm::ivec2{6, 13}) { addChild(&mBackground); addChild(&mGrid); diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp index 067352199..6f7ec1923 100644 --- a/es-app/src/guis/GuiScraperMulti.cpp +++ b/es-app/src/guis/GuiScraperMulti.cpp @@ -86,19 +86,19 @@ GuiScraperMulti::GuiScraperMulti(Window* window, // Previously refined. if (mSearchComp->getRefinedSearch()) allowRefine = true; - // Interactive mode and "Auto-accept single game matches" not enabled. + // Interactive mode and "Auto-accept single game matches" not enabled. else if (mSearchComp->getSearchType() != GuiScraperSearch::ACCEPT_SINGLE_MATCHES) allowRefine = true; - // Interactive mode with "Auto-accept single game matches" enabled and more - // than one result. + // Interactive mode with "Auto-accept single game matches" enabled and more + // than one result. else if (mSearchComp->getSearchType() == - GuiScraperSearch::ACCEPT_SINGLE_MATCHES && + GuiScraperSearch::ACCEPT_SINGLE_MATCHES && mSearchComp->getScraperResultsSize() > 1) allowRefine = true; - // Dito but there were no games found, or the search has not been completed. + // Dito but there were no games found, or the search has not been completed. else if (mSearchComp->getSearchType() == - GuiScraperSearch::ACCEPT_SINGLE_MATCHES && + GuiScraperSearch::ACCEPT_SINGLE_MATCHES && !mSearchComp->getFoundGame()) allowRefine = true; diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index d749c1302..1f5b9a1c9 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -37,15 +37,9 @@ #define FAILED_VERIFICATION_RETRIES 8 -GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int scrapeCount) - : GuiComponent(window) - , mGrid(window, glm::ivec2{4, 3}) - , mSearchType(type) - , mScrapeCount(scrapeCount) - , mRefinedSearch(false) - , mFoundGame(false) - , mScrapeRatings(false) - , mBusyAnim(window) +GuiScraperSearch::GuiScraperSearch(Window *window, SearchType type, unsigned int scrapeCount) + : GuiComponent(window), mGrid(window, glm::ivec2{4, 3}), mSearchType(type), mScrapeCount(scrapeCount), + mRefinedSearch(false), mFoundGame(false), mScrapeRatings(false), mBusyAnim(window) { addChild(&mGrid); @@ -473,14 +467,14 @@ void GuiScraperSearch::updateInfoPane() i = 0; if (i != -1 && static_cast(mScraperResults.size()) > i) { - ScraperSearchResult& res = mScraperResults.at(i); + ScraperSearchResult &res = mScraperResults.at(i); mResultName->setText(Utils::String::toUpper(res.mdl.get("name"))); mResultDesc->setText(Utils::String::toUpper(res.mdl.get("desc"))); mDescContainer->reset(); mResultThumbnail->setImage(""); - const std::string& thumb = res.screenshotUrl.empty() ? res.coverUrl : res.screenshotUrl; + const std::string &thumb = res.screenshotUrl.empty() ? res.coverUrl : res.screenshotUrl; mScraperResults[i].thumbnailImageUrl = thumb; // Cache the thumbnail image in mScraperResults so that we don't need to download @@ -553,13 +547,13 @@ bool GuiScraperSearch::input(InputConfig* config, Input input) // Previously refined. if (mRefinedSearch) allowRefine = true; - // Interactive mode and "Auto-accept single game matches" not enabled. + // Interactive mode and "Auto-accept single game matches" not enabled. else if (mSearchType != ACCEPT_SINGLE_MATCHES) allowRefine = true; - // Interactive mode with "Auto-accept single game matches" enabled and more than one result. + // Interactive mode with "Auto-accept single game matches" enabled and more than one result. else if (mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1) allowRefine = true; - // Dito but there were no games found, or the search has not been completed. + // Dito but there were no games found, or the search has not been completed. else if (mSearchType == ACCEPT_SINGLE_MATCHES && !mFoundGame) allowRefine = true; @@ -790,16 +784,15 @@ void GuiScraperSearch::updateThumbnail() } } -void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) -{ +void GuiScraperSearch::openInputScreen(ScraperSearchParams ¶ms) { auto searchForFunc = [&](std::string name) { // Trim leading and trailing whitespaces. name.erase(name.begin(), std::find_if(name.begin(), name.end(), [](char c) { - return !std::isspace(static_cast(c)); - })); + return !std::isspace(static_cast(c)); + })); name.erase(std::find_if(name.rbegin(), name.rend(), [](char c) { return !std::isspace(static_cast(c)); }) - .base(), + .base(), name.end()); stop(); @@ -817,8 +810,7 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) // regardless of whether the entry is an arcade game and TheGamesDB is used. if (Settings::getInstance()->getBool("ScraperSearchMetadataName")) { searchString = Utils::String::removeParenthesis(params.game->metadata.get("name")); - } - else { + } else { // If searching based on the actual file name, then expand to the full game name // in case the scraper is set to TheGamesDB and it's an arcade game. This is // required as TheGamesDB does not support searches using the short MAME names. @@ -828,8 +820,7 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) else searchString = params.game->getCleanName(); } - } - else { + } else { searchString = params.nameOverride; } @@ -837,8 +828,7 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), "REFINE SEARCH", searchString, searchForFunc, false, "SEARCH", "SEARCH USING REFINED NAME?")); - } - else { + } else { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH", searchString, searchForFunc, false, "SEARCH", "SEARCH USING REFINED NAME?")); @@ -928,8 +918,7 @@ bool GuiScraperSearch::saveMetadata(const ScraperSearchResult& result, return metadataUpdated; } -std::vector GuiScraperSearch::getHelpPrompts() -{ +std::vector GuiScraperSearch::getHelpPrompts() { std::vector prompts; prompts.push_back(HelpPrompt("y", "refine search")); diff --git a/es-app/src/guis/GuiScraperSearch.h b/es-app/src/guis/GuiScraperSearch.h index 449124ba0..0cf1dfd3b 100644 --- a/es-app/src/guis/GuiScraperSearch.h +++ b/es-app/src/guis/GuiScraperSearch.h @@ -63,39 +63,51 @@ public: { mAcceptCallback = acceptCallback; } - void setSkipCallback(const std::function& skipCallback) - { + + void setSkipCallback(const std::function &skipCallback) { mSkipCallback = skipCallback; } - void setCancelCallback(const std::function& cancelCallback) - { + + void setCancelCallback(const std::function &cancelCallback) { mCancelCallback = cancelCallback; } - bool input(InputConfig* config, Input input) override; + bool input(InputConfig *config, Input input) override; + void update(int deltaTime) override; - void render(const glm::mat4& parentTrans) override; + + void render(const glm::mat4 &parentTrans) override; + std::vector getHelpPrompts() override; + HelpStyle getHelpStyle() override; + void onSizeChanged() override; - void decreaseScrapeCount() - { + void decreaseScrapeCount() { if (mScrapeCount > 0) mScrapeCount--; } + void unsetRefinedSearch() { mRefinedSearch = false; } + bool getRefinedSearch() { return mRefinedSearch; } + bool getFoundGame() { return mFoundGame; } - const std::string& getNameOverride() { return mLastSearch.nameOverride; } + + const std::string &getNameOverride() { return mLastSearch.nameOverride; } void onFocusGained() override { mGrid.onFocusGained(); } + void onFocusLost() override { mGrid.onFocusLost(); } private: void updateViewStyle(); + void updateThumbnail(); + void updateInfoPane(); + void resizeMetadata(); void onSearchError(const std::string& error, diff --git a/es-app/src/guis/GuiSettings.cpp b/es-app/src/guis/GuiSettings.cpp index 6cd181dc8..cbfbd2197 100644 --- a/es-app/src/guis/GuiSettings.cpp +++ b/es-app/src/guis/GuiSettings.cpp @@ -21,21 +21,11 @@ #include "views/ViewController.h" #include "views/gamelist/IGameListView.h" -GuiSettings::GuiSettings(Window* window, std::string title) - : GuiComponent(window) - , mMenu(window, title) - , mGoToSystem(nullptr) - , mNeedsSaving(false) - , mNeedsReloadHelpPrompts(false) - , mNeedsCollectionsUpdate(false) - , mNeedsSorting(false) - , mNeedsSortingCollections(false) - , mNeedsResetFilters(false) - , mNeedsReloading(false) - , mNeedsGoToStart(false) - , mNeedsGoToSystem(false) - , mNeedsGoToGroupedCollections(false) - , mInvalidateCachedBackground(false) +GuiSettings::GuiSettings(Window *window, std::string title) + : GuiComponent(window), mMenu(window, title), mGoToSystem(nullptr), mNeedsSaving(false), + mNeedsReloadHelpPrompts(false), mNeedsCollectionsUpdate(false), mNeedsSorting(false), + mNeedsSortingCollections(false), mNeedsResetFilters(false), mNeedsReloading(false), mNeedsGoToStart(false), + mNeedsGoToSystem(false), mNeedsGoToGroupedCollections(false), mInvalidateCachedBackground(false) { addChild(&mMenu); mMenu.addButton("BACK", "back", [this] { delete this; }); @@ -184,12 +174,10 @@ void GuiSettings::addEditableTextComponent(const std::string label, else if (isPassword && newVal == "") { ed->setValue(""); ed->setHiddenValue(""); - } - else if (isPassword) { + } else if (isPassword) { ed->setValue("********"); ed->setHiddenValue(newVal); - } - else { + } else { ed->setValue(newVal); } }; @@ -199,14 +187,13 @@ void GuiSettings::addEditableTextComponent(const std::string label, // Never display the value if it's a password, instead set it to blank. if (isPassword) mWindow->pushGui(new GuiTextEditKeyboardPopup( - mWindow, getHelpStyle(), label, "", updateVal, false, "SAVE", "SAVE CHANGES?")); + mWindow, getHelpStyle(), label, "", updateVal, false, "SAVE", "SAVE CHANGES?")); else mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), label, ed->getValue(), updateVal, false, "SAVE", "SAVE CHANGES?")); }); - } - else { + } else { row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { if (isPassword) mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, "", updateVal, diff --git a/es-app/src/scrapers/GamesDBJSONScraper.cpp b/es-app/src/scrapers/GamesDBJSONScraper.cpp index 4c015d38c..87dedbac7 100644 --- a/es-app/src/scrapers/GamesDBJSONScraper.cpp +++ b/es-app/src/scrapers/GamesDBJSONScraper.cpp @@ -147,8 +147,7 @@ void thegamesdb_generate_json_scraper_requests( // using this regardless of whether the entry is an arcade game. if (Settings::getInstance()->getBool("ScraperSearchMetadataName")) { cleanName = Utils::String::removeParenthesis(params.game->metadata.get("name")); - } - else { + } else { // If not searching based on the metadata name, then check whether it's an // arcade game and if so expand to the full game name. This is required as // TheGamesDB has issues with searching using the short MAME names. @@ -165,10 +164,10 @@ void thegamesdb_generate_json_scraper_requests( return !std::isspace(static_cast(c)); })); cleanName.erase( - std::find_if(cleanName.rbegin(), cleanName.rend(), - [](char c) { return !std::isspace(static_cast(c)); }) - .base(), - cleanName.end()); + std::find_if(cleanName.rbegin(), cleanName.rend(), + [](char c) { return !std::isspace(static_cast(c)); }) + .base(), + cleanName.end()); path += "/Games/ByGameName?" + apiKey + "&fields=players,publishers,genres,overview,last_updated,rating," @@ -457,7 +456,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr& req, if (doc.HasMember("remaining_monthly_allowance") && doc.HasMember("extra_allowance")) { for (size_t i = 0; i < results.size(); i++) { results[i].scraperRequestAllowance = - doc["remaining_monthly_allowance"].GetInt() + doc["extra_allowance"].GetInt(); + doc["remaining_monthly_allowance"].GetInt() + doc["extra_allowance"].GetInt(); } LOG(LogDebug) << "TheGamesDBJSONRequest::process(): " "Remaining monthly scraping allowance: " diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 5ca9befec..f2aac8148 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -17,32 +17,12 @@ #define FADE_IN_TIME 650 DetailedGameListView::DetailedGameListView(Window* window, FileData* root) - : BasicGameListView(window, root) - , mThumbnail(window) - , mMarquee(window) - , mImage(window) - , mLblRating(window) - , mLblReleaseDate(window) - , mLblDeveloper(window) - , mLblPublisher(window) - , mLblGenre(window) - , mLblPlayers(window) - , mLblLastPlayed(window) - , mLblPlayCount(window) - , mBadges(window) - , mRating(window) - , mReleaseDate(window) - , mDeveloper(window) - , mPublisher(window) - , mGenre(window) - , mPlayers(window) - , mLastPlayed(window) - , mPlayCount(window) - , mName(window) - , mDescContainer(window) - , mDescription(window) - , mGamelistInfo(window) - , mLastUpdated(nullptr) + : BasicGameListView(window, root), mThumbnail(window), mMarquee(window), mImage(window), mLblRating(window), + mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window), mLblGenre(window), mLblPlayers(window), + mLblLastPlayed(window), mLblPlayCount(window), mBadges(window), mRating(window), mReleaseDate(window), + mDeveloper(window), + mPublisher(window), mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window), mName(window), + mDescContainer(window), mDescription(window), mGamelistInfo(window), mLastUpdated(nullptr) { const float padding = 0.01f; diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index 4f9491f8a..319029bce 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -20,31 +20,12 @@ #define FADE_IN_TIME 650 GridGameListView::GridGameListView(Window* window, FileData* root) - : ISimpleGameListView(window, root) - , mGrid(window) - , mMarquee(window) - , mImage(window) - , mLblRating(window) - , mLblReleaseDate(window) - , mLblDeveloper(window) - , mLblPublisher(window) - , mLblGenre(window) - , mLblPlayers(window) - , mLblLastPlayed(window) - , mLblPlayCount(window) - , mBadges(window) - , mRating(window) - , mReleaseDate(window) - , mDeveloper(window) - , mPublisher(window) - , mGenre(window) - , mPlayers(window) - , mLastPlayed(window) - , mPlayCount(window) - , mName(window) - , mDescContainer(window) - , mDescription(window) - , mGamelistInfo(window) + : ISimpleGameListView(window, root), mGrid(window), mMarquee(window), mImage(window), mLblRating(window), + mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window), mLblGenre(window), mLblPlayers(window), + mLblLastPlayed(window), mLblPlayCount(window), mBadges(window), mRating(window), mReleaseDate(window), + mDeveloper(window), + mPublisher(window), mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window), mName(window), + mDescContainer(window), mDescription(window), mGamelistInfo(window) { const float padding = 0.01f; diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index 5075fc311..5790a3cb0 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -16,22 +16,11 @@ #include -GuiComponent::GuiComponent(Window* window) - : mWindow(window) - , mParent(nullptr) - , mOpacity(255) - , mColor(0) - , mSaturation(1.0f) - , mColorShift(0) - , mColorShiftEnd(0) - , mPosition({}) - , mOrigin({}) - , mRotationOrigin(0.5f, 0.5f) - , mSize({}) - , mIsProcessing(false) - , mVisible(true) - , mEnabled(true) - , mTransform(Renderer::getIdentity()) +GuiComponent::GuiComponent(Window *window) + : mWindow(window), mParent(nullptr), mOpacity(255), mColor(0), mSaturation(1.0f), mColorShift(0), + mColorShiftEnd(0), + mPosition({}), mOrigin({}), mRotationOrigin(0.5f, 0.5f), mSize({}), mIsProcessing(false), mVisible(true), + mEnabled(true), mTransform(Renderer::getIdentity()) { for (unsigned char i = 0; i < MAX_ANIMATIONS; i++) mAnimationMap[i] = nullptr; diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 322efab40..386b78ef0 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -230,14 +230,15 @@ public: const static unsigned char MAX_ANIMATIONS = 4; protected: - void renderChildren(const glm::mat4& transform) const; + void renderChildren(const glm::mat4 &transform) const; + void updateSelf(int deltaTime); // Updates animations. void updateChildren(int deltaTime); // Updates animations. - Window* mWindow; + Window *mWindow; - GuiComponent* mParent; - std::vector mChildren; + GuiComponent *mParent; + std::vector mChildren; unsigned char mOpacity; unsigned int mColor; diff --git a/es-core/src/HttpReq.h b/es-core/src/HttpReq.h index d0b9f38ee..78feb529a 100644 --- a/es-core/src/HttpReq.h +++ b/es-core/src/HttpReq.h @@ -80,7 +80,7 @@ private: static CURLM* s_multi_handle; Status mStatus; - CURL* mHandle; + CURL *mHandle; std::stringstream mContent; std::string mErrorMsg; diff --git a/es-core/src/components/ButtonComponent.cpp b/es-core/src/components/ButtonComponent.cpp index 3143f52a9..94734a0b7 100644 --- a/es-core/src/components/ButtonComponent.cpp +++ b/es-core/src/components/ButtonComponent.cpp @@ -12,25 +12,15 @@ #include "resources/Font.h" #include "utils/StringUtil.h" -ButtonComponent::ButtonComponent(Window* window, - const std::string& text, - const std::string& helpText, - const std::function& func, +ButtonComponent::ButtonComponent(Window *window, + const std::string &text, + const std::string &helpText, + const std::function &func, bool upperCase, bool flatStyle) - : GuiComponent{window} - , mBox{window, ":/graphics/button.svg"} - , mFont{Font::get(FONT_SIZE_MEDIUM)} - , mPadding{{}} - , mFocused{false} - , mEnabled{true} - , mFlatStyle{flatStyle} - , mTextColorFocused{0xFFFFFFFF} - , mTextColorUnfocused{0x777777FF} - , mFlatColorFocused{0x878787FF} - , mFlatColorUnfocused{0x60606025} - -{ + : GuiComponent{window}, mBox{window, ":/graphics/button.svg"}, mFont{Font::get(FONT_SIZE_MEDIUM)}, mPadding{{}}, + mFocused{false}, mEnabled{true}, mFlatStyle{flatStyle}, mTextColorFocused{0xFFFFFFFF}, + mTextColorUnfocused{0x777777FF}, mFlatColorFocused{0x878787FF}, mFlatColorUnfocused{0x60606025} { setPressedFunc(func); setText(text, helpText, upperCase); @@ -38,8 +28,7 @@ ButtonComponent::ButtonComponent(Window* window, updateImage(); } -void ButtonComponent::onSizeChanged() -{ +void ButtonComponent::onSizeChanged() { if (mFlatStyle) return; @@ -50,27 +39,24 @@ void ButtonComponent::onSizeChanged() glm::vec2{-cornerSize.x * 2.0f, -cornerSize.y * 2.0f}); } -void ButtonComponent::onFocusGained() -{ +void ButtonComponent::onFocusGained() { mFocused = true; if (!mFlatStyle) updateImage(); } -void ButtonComponent::onFocusLost() -{ +void ButtonComponent::onFocusLost() { mFocused = false; if (!mFlatStyle) updateImage(); } -void ButtonComponent::setText(const std::string& text, const std::string& helpText, bool upperCase) -{ +void ButtonComponent::setText(const std::string &text, const std::string &helpText, bool upperCase) { mText = upperCase ? Utils::String::toUpper(text) : text; mHelpText = helpText; mTextCache = - std::unique_ptr(mFont->buildTextCache(mText, 0.0f, 0.0f, getCurTextColor())); + std::unique_ptr(mFont->buildTextCache(mText, 0.0f, 0.0f, getCurTextColor())); float minWidth = mFont->sizeText("DELETE").x + (12.0f * Renderer::getScreenWidthModifier()); setSize(std::max(mTextCache->metrics.size.x + (12.0f * Renderer::getScreenWidthModifier()), @@ -80,15 +66,13 @@ void ButtonComponent::setText(const std::string& text, const std::string& helpTe updateHelpPrompts(); } -void ButtonComponent::setEnabled(bool state) -{ +void ButtonComponent::setEnabled(bool state) { mEnabled = state; if (!mFlatStyle) updateImage(); } -void ButtonComponent::setPadding(const glm::vec4 padding) -{ +void ButtonComponent::setPadding(const glm::vec4 padding) { if (mPadding == padding) return; @@ -96,8 +80,7 @@ void ButtonComponent::setPadding(const glm::vec4 padding) onSizeChanged(); } -bool ButtonComponent::input(InputConfig* config, Input input) -{ +bool ButtonComponent::input(InputConfig *config, Input input) { if (config->isMappedTo("a", input) && input.value != 0) { if (mPressedFunc && mEnabled) mPressedFunc(); @@ -107,8 +90,7 @@ bool ButtonComponent::input(InputConfig* config, Input input) return GuiComponent::input(config, input); } -void ButtonComponent::render(const glm::mat4& parentTrans) -{ +void ButtonComponent::render(const glm::mat4 &parentTrans) { glm::mat4 trans{parentTrans * getTransform()}; if (mFlatStyle) { @@ -117,15 +99,13 @@ void ButtonComponent::render(const glm::mat4& parentTrans) Renderer::drawRect(mPadding.x, mPadding.y, mSize.x - mPadding.x - mPadding.z, mSize.y - mPadding.y - mPadding.w, mFlatColorFocused, mFlatColorFocused); - } - else { + } else { Renderer::setMatrix(trans); Renderer::drawRect(mPadding.x, mPadding.y, mSize.x - mPadding.x - mPadding.z, mSize.y - mPadding.y - mPadding.w, mFlatColorUnfocused, mFlatColorUnfocused); } - } - else { + } else { mBox.render(trans); } @@ -151,23 +131,20 @@ void ButtonComponent::render(const glm::mat4& parentTrans) renderChildren(trans); } -std::vector ButtonComponent::getHelpPrompts() -{ +std::vector ButtonComponent::getHelpPrompts() { std::vector prompts; prompts.push_back(HelpPrompt("a", mHelpText.empty() ? mText.c_str() : mHelpText.c_str())); return prompts; } -unsigned int ButtonComponent::getCurTextColor() const -{ +unsigned int ButtonComponent::getCurTextColor() const { if (!mFocused) return mTextColorUnfocused; else return mTextColorFocused; } -void ButtonComponent::updateImage() -{ +void ButtonComponent::updateImage() { if (!mEnabled || !mPressedFunc) { mBox.setImagePath(":/graphics/button_filled.svg"); mBox.setCenterColor(0x770000FF); diff --git a/es-core/src/components/ButtonComponent.h b/es-core/src/components/ButtonComponent.h index 55ad497db..7b1dad9a3 100644 --- a/es-core/src/components/ButtonComponent.h +++ b/es-core/src/components/ButtonComponent.h @@ -14,41 +14,48 @@ class TextCache; -class ButtonComponent : public GuiComponent -{ +class ButtonComponent : public GuiComponent { public: - ButtonComponent(Window* window, - const std::string& text = "", - const std::string& helpText = "", - const std::function& func = nullptr, + ButtonComponent(Window *window, + const std::string &text = "", + const std::string &helpText = "", + const std::function &func = nullptr, bool upperCase = true, bool flatStyle = false); void onSizeChanged() override; + void onFocusGained() override; + void onFocusLost() override; - void setText(const std::string& text, const std::string& helpText, bool upperCase = true); - const std::string& getText() const { return mText; } + void setText(const std::string &text, const std::string &helpText, bool upperCase = true); + + const std::string &getText() const { return mText; } void setPressedFunc(std::function f) { mPressedFunc = f; } + void setEnabled(bool state) override; void setPadding(const glm::vec4 padding); + glm::vec4 getPadding() { return mPadding; } void setFlatColorFocused(unsigned int color) { mFlatColorFocused = color; } + void setFlatColorUnfocused(unsigned int color) { mFlatColorUnfocused = color; } - const std::function& getPressedFunc() const { return mPressedFunc; } + const std::function &getPressedFunc() const { return mPressedFunc; } - bool input(InputConfig* config, Input input) override; - void render(const glm::mat4& parentTrans) override; + bool input(InputConfig *config, Input input) override; + + void render(const glm::mat4 &parentTrans) override; virtual std::vector getHelpPrompts() override; private: unsigned int getCurTextColor() const; + void updateImage(); NinePatchComponent mBox; diff --git a/es-core/src/components/ComponentGrid.cpp b/es-core/src/components/ComponentGrid.cpp index bd6012d12..3f73e93c0 100644 --- a/es-core/src/components/ComponentGrid.cpp +++ b/es-core/src/components/ComponentGrid.cpp @@ -245,7 +245,7 @@ const ComponentGrid::GridEntry* ComponentGrid::getCellAt(int x, int y) const bool ComponentGrid::input(InputConfig* config, Input input) { - const GridEntry* cursorEntry = getCellAt(mCursor); + const GridEntry *cursorEntry = getCellAt(mCursor); if (cursorEntry && cursorEntry->component->input(config, input)) return true; @@ -287,12 +287,11 @@ void ComponentGrid::resetCursor() } } -bool ComponentGrid::moveCursor(glm::ivec2 dir) -{ +bool ComponentGrid::moveCursor(glm::ivec2 dir) { assert(dir.x || dir.y); const glm::ivec2 origCursor{mCursor}; - const GridEntry* currentCursorEntry = getCellAt(mCursor); + const GridEntry *currentCursorEntry = getCellAt(mCursor); glm::ivec2 searchAxis(dir.x == 0, dir.y == 0); // Logic to handle entries that span several cells. @@ -326,7 +325,7 @@ bool ComponentGrid::moveCursor(glm::ivec2 dir) while (mCursor.x >= 0 && mCursor.y >= 0 && mCursor.x < mGridSize.x && mCursor.y < mGridSize.y) { mCursor = mCursor + dir; glm::ivec2 curDirPos{mCursor}; - const GridEntry* cursorEntry; + const GridEntry *cursorEntry; // Spread out on search axis+ while (mCursor.x < mGridSize.x && mCursor.y < mGridSize.y && mCursor.x >= 0 && @@ -368,8 +367,7 @@ bool ComponentGrid::moveCursor(glm::ivec2 dir) return false; } -void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell) -{ +void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell) { const glm::ivec2 origCursor{mCursor}; if (xPos != -1) @@ -377,7 +375,7 @@ void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell) if (yPos != -1) mCursor.y = yPos; - const GridEntry* currentCursorEntry = getCellAt(mCursor); + const GridEntry *currentCursorEntry = getCellAt(mCursor); // If requested, select the leftmost cell of entries wider than 1 cell. if (selectLeftCell && mCursor.x > currentCursorEntry->pos.x) @@ -386,16 +384,14 @@ void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell) onCursorMoved(origCursor, mCursor); } -void ComponentGrid::onFocusLost() -{ - const GridEntry* cursorEntry = getCellAt(mCursor); +void ComponentGrid::onFocusLost() { + const GridEntry *cursorEntry = getCellAt(mCursor); if (cursorEntry) cursorEntry->component->onFocusLost(); } -void ComponentGrid::onFocusGained() -{ - const GridEntry* cursorEntry = getCellAt(mCursor); +void ComponentGrid::onFocusGained() { + const GridEntry *cursorEntry = getCellAt(mCursor); if (cursorEntry) cursorEntry->component->onFocusGained(); } diff --git a/es-core/src/components/ComponentGrid.h b/es-core/src/components/ComponentGrid.h index fc1145108..59f1ca852 100644 --- a/es-core/src/components/ComponentGrid.h +++ b/es-core/src/components/ComponentGrid.h @@ -30,53 +30,60 @@ namespace GridFlags } // namespace GridFlags // Provides basic layout of components in an X*Y grid. -class ComponentGrid : public GuiComponent -{ +class ComponentGrid : public GuiComponent { public: - ComponentGrid(Window* window, const glm::ivec2& gridDimensions); + ComponentGrid(Window *window, const glm::ivec2 &gridDimensions); + virtual ~ComponentGrid(); - bool removeEntry(const std::shared_ptr& comp); + bool removeEntry(const std::shared_ptr &comp); - void setEntry(const std::shared_ptr& comp, - const glm::ivec2& pos, + void setEntry(const std::shared_ptr &comp, + const glm::ivec2 &pos, bool canFocus, bool resize = true, - const glm::ivec2& size = glm::ivec2{1, 1}, + const glm::ivec2 &size = glm::ivec2{1, 1}, unsigned int border = GridFlags::BORDER_NONE, GridFlags::UpdateType updateType = GridFlags::UPDATE_ALWAYS); - void setPastBoundaryCallback(const std::function& func) - { + void setPastBoundaryCallback(const std::function &func) { mPastBoundaryCallback = func; } - void textInput(const std::string& text) override; - bool input(InputConfig* config, Input input) override; + void textInput(const std::string &text) override; + + bool input(InputConfig *config, Input input) override; + void update(int deltaTime) override; - void render(const glm::mat4& parentTrans) override; + + void render(const glm::mat4 &parentTrans) override; + void onSizeChanged() override; void resetCursor(); + bool cursorValid(); float getColWidth(int col); + float getRowHeight(int row); // If update is false, will not call an onSizeChanged() which triggers // a (potentially costly) repositioning + resizing of every element. void setColWidthPerc(int col, float width, bool update = true); + // Dito. void setRowHeightPerc(int row, float height, bool update = true); bool moveCursor(glm::ivec2 dir); + // Pass -1 for xPos or yPos to keep its axis cursor position. void moveCursorTo(int xPos, int yPos, bool selectLeftCell = false); - void setCursorTo(const std::shared_ptr& comp); - std::shared_ptr getSelectedComponent() - { - const GridEntry* e = getCellAt(mCursor); + void setCursorTo(const std::shared_ptr &comp); + + std::shared_ptr getSelectedComponent() { + const GridEntry *e = getCellAt(mCursor); if (e) return e->component; else @@ -84,6 +91,7 @@ public: } void onFocusLost() override; + void onFocusGained() override; virtual std::vector getHelpPrompts() override; @@ -121,22 +129,25 @@ private: }; // Update position and size. - void updateCellComponent(const GridEntry& cell); + void updateCellComponent(const GridEntry &cell); + void updateSeparators(); void onCursorMoved(glm::ivec2 from, glm::ivec2 to); - const GridEntry* getCellAt(int x, int y) const; - const GridEntry* getCellAt(const glm::ivec2& pos) const { return getCellAt(pos.x, pos.y); } + + const GridEntry *getCellAt(int x, int y) const; + + const GridEntry *getCellAt(const glm::ivec2 &pos) const { return getCellAt(pos.x, pos.y); } std::vector> mSeparators; glm::ivec2 mGridSize; std::vector mCells; glm::ivec2 mCursor; - std::function mPastBoundaryCallback; + std::function mPastBoundaryCallback; - float* mRowHeights; - float* mColWidths; + float *mRowHeights; + float *mColWidths; }; #endif // ES_CORE_COMPONENTS_COMPONENT_GRID_H diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index db6e65fff..230e66500 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -71,11 +71,10 @@ bool ComponentList::input(InputConfig* config, Input input) if (mEntries.at(mCursor).data.input_handler) { if (mEntries.at(mCursor).data.input_handler(config, input)) return true; - } - else { + } else { // No input handler assigned, do the default, which is to give it // to the rightmost element in the row. - auto& row = mEntries.at(mCursor).data; + auto &row = mEntries.at(mCursor).data; if (row.elements.size()) { if (row.elements.back().component->input(config, input)) return true; @@ -197,7 +196,7 @@ void ComponentList::render(const glm::mat4& parentTrans) std::vector drawAfterCursor; bool drawAll; for (size_t i = 0; i < mEntries.size(); i++) { - auto& entry = mEntries.at(i); + auto &entry = mEntries.at(i); drawAll = !mFocused || i != static_cast(mCursor); for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); it++) { if (drawAll || it->invert_when_selected) { diff --git a/es-core/src/components/DateTimeEditComponent.cpp b/es-core/src/components/DateTimeEditComponent.cpp index 23b900aae..b1d6b3664 100644 --- a/es-core/src/components/DateTimeEditComponent.cpp +++ b/es-core/src/components/DateTimeEditComponent.cpp @@ -12,17 +12,10 @@ #include "resources/Font.h" #include "utils/StringUtil.h" -DateTimeEditComponent::DateTimeEditComponent(Window* window, bool alignRight, DisplayMode dispMode) - : GuiComponent(window) - , mEditing(false) - , mEditIndex(0) - , mDisplayMode(dispMode) - , mRelativeUpdateAccumulator(0) - , mColor(0x777777FF) - , mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)) - , mAlignRight(alignRight) - , mUppercase(false) - , mAutoSize(true) +DateTimeEditComponent::DateTimeEditComponent(Window *window, bool alignRight, DisplayMode dispMode) + : GuiComponent(window), mEditing(false), mEditIndex(0), mDisplayMode(dispMode), mRelativeUpdateAccumulator(0), + mColor(0x777777FF), mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)), mAlignRight(alignRight), + mUppercase(false), mAutoSize(true) { updateTextCache(); } diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 2daa686df..37e26a952 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -24,43 +24,45 @@ FlexboxComponent::FlexboxComponent(Window* window) // Initialize item margins. mItemMargin = glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}; - // Calculate flexbox layout. - computeLayout(); + // Layout validity + mLayoutValid = false; } // Getters/Setters for rendering options. void FlexboxComponent::setDirection(std::string value) { mDirection = value; - computeLayout(); + mLayoutValid = false; } std::string FlexboxComponent::getDirection() { return mDirection; } void FlexboxComponent::setAlign(std::string value) { mAlign = value; - computeLayout(); + mLayoutValid = false; } std::string FlexboxComponent::getAlign() { return mAlign; } void FlexboxComponent::setItemsPerLine(unsigned int value) { mItemsPerLine = value; - computeLayout(); + mLayoutValid = false; } unsigned int FlexboxComponent::getItemsPerLine() { return mItemsPerLine; } void FlexboxComponent::setItemMargin(glm::vec2 value) { mItemMargin = value; - computeLayout(); + mLayoutValid = false; } glm::vec2 FlexboxComponent::getItemMargin() { return mItemMargin; } void FlexboxComponent::setItemWidth(float value) { mItemWidth = value; - computeLayout(); + mLayoutValid = false; } float FlexboxComponent::getItemWidth() { return mItemWidth; } -void FlexboxComponent::onSizeChanged() { computeLayout(); } +void FlexboxComponent::onSizeChanged() { + mLayoutValid = false; +} void FlexboxComponent::computeLayout() { @@ -139,20 +141,23 @@ void FlexboxComponent::computeLayout() if (directionRow.x == 0) { anchorY += lineWidth * directionRow.y; anchorX = anchorXStart; - } - else { + } else { anchorX += lineWidth * directionRow.x; anchorY = anchorYStart; } } } + + mLayoutValid = true; } -void FlexboxComponent::render(const glm::mat4& parentTrans) -{ +void FlexboxComponent::render(const glm::mat4& parentTrans) { if (!isVisible()) return; + if (!mLayoutValid) + computeLayout(); + renderChildren(parentTrans); } @@ -189,8 +194,8 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, GuiComponent::applyTheme(theme, view, element, properties); - // Trigger layout computation. - onSizeChanged(); + // Layout no longer valid. + mLayoutValid = false; } std::vector FlexboxComponent::getHelpPrompts() diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index 45c82637c..731a00c8e 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -66,6 +66,7 @@ private: unsigned int mItemsPerLine; glm::vec2 mItemMargin; float mItemWidth; + bool mLayoutValid; }; #endif // ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index 69c1ae32c..3729f5a32 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -37,36 +37,36 @@ void HelpComponent::assignIcons() ":/help/dpad_updown.svg" : mStyle.mCustomButtons.dpad_updown; sIconPathMap["left/right"] = mStyle.mCustomButtons.dpad_leftright.empty() ? - ":/help/dpad_leftright.svg" : - mStyle.mCustomButtons.dpad_leftright; + ":/help/dpad_leftright.svg" : + mStyle.mCustomButtons.dpad_leftright; sIconPathMap["up/down/left/right"] = mStyle.mCustomButtons.dpad_all.empty() ? - ":/help/dpad_all.svg" : - mStyle.mCustomButtons.dpad_all; + ":/help/dpad_all.svg" : + mStyle.mCustomButtons.dpad_all; sIconPathMap["thumbstickclick"] = mStyle.mCustomButtons.thumbstick_click.empty() ? - ":/help/thumbstick_click.svg" : - mStyle.mCustomButtons.thumbstick_click; + ":/help/thumbstick_click.svg" : + mStyle.mCustomButtons.thumbstick_click; sIconPathMap["l"] = mStyle.mCustomButtons.button_l.empty() ? ":/help/button_l.svg" : - mStyle.mCustomButtons.button_l; + mStyle.mCustomButtons.button_l; sIconPathMap["r"] = mStyle.mCustomButtons.button_r.empty() ? ":/help/button_r.svg" : - mStyle.mCustomButtons.button_r; + mStyle.mCustomButtons.button_r; sIconPathMap["lr"] = mStyle.mCustomButtons.button_lr.empty() ? ":/help/button_lr.svg" : - mStyle.mCustomButtons.button_lr; + mStyle.mCustomButtons.button_lr; sIconPathMap["lt"] = mStyle.mCustomButtons.button_lt.empty() ? ":/help/button_lt.svg" : - mStyle.mCustomButtons.button_lt; + mStyle.mCustomButtons.button_lt; sIconPathMap["rt"] = mStyle.mCustomButtons.button_rt.empty() ? ":/help/button_rt.svg" : - mStyle.mCustomButtons.button_rt; + mStyle.mCustomButtons.button_rt; // These graphics files are custom per controller type. if (controllerType == "snes") { sIconPathMap["a"] = mStyle.mCustomButtons.button_a_SNES.empty() ? - ":/help/button_a_SNES.svg" : - mStyle.mCustomButtons.button_a_SNES; + ":/help/button_a_SNES.svg" : + mStyle.mCustomButtons.button_a_SNES; sIconPathMap["b"] = mStyle.mCustomButtons.button_b_SNES.empty() ? - ":/help/button_b_SNES.svg" : - mStyle.mCustomButtons.button_b_SNES; + ":/help/button_b_SNES.svg" : + mStyle.mCustomButtons.button_b_SNES; sIconPathMap["x"] = mStyle.mCustomButtons.button_x_SNES.empty() ? - ":/help/button_x_SNES.svg" : - mStyle.mCustomButtons.button_x_SNES; + ":/help/button_x_SNES.svg" : + mStyle.mCustomButtons.button_x_SNES; sIconPathMap["y"] = mStyle.mCustomButtons.button_y_SNES.empty() ? ":/help/button_y_SNES.svg" : mStyle.mCustomButtons.button_y_SNES; diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 600af7c22..5891074d1 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -27,23 +27,11 @@ glm::vec2 ImageComponent::getSize() const return GuiComponent::getSize() * (mBottomRightCrop - mTopLeftCrop); } -ImageComponent::ImageComponent(Window* window, bool forceLoad, bool dynamic) - : GuiComponent(window) - , mTargetSize({}) - , mFlipX(false) - , mFlipY(false) - , mTargetIsMax(false) - , mTargetIsMin(false) - , mColorShift(0xFFFFFFFF) - , mColorShiftEnd(0xFFFFFFFF) - , mColorGradientHorizontal(true) - , mFadeOpacity(0) - , mFading(false) - , mForceLoad(forceLoad) - , mDynamic(dynamic) - , mRotateByTargetSize(false) - , mTopLeftCrop({}) - , mBottomRightCrop(1.0f, 1.0f) +ImageComponent::ImageComponent(Window *window, bool forceLoad, bool dynamic) + : GuiComponent(window), mTargetSize({}), mFlipX(false), mFlipY(false), mTargetIsMax(false), mTargetIsMin(false), + mColorShift(0xFFFFFFFF), mColorShiftEnd(0xFFFFFFFF), mColorGradientHorizontal(true), mFadeOpacity(0), + mFading(false), mForceLoad(forceLoad), mDynamic(dynamic), mRotateByTargetSize(false), mTopLeftCrop({}), + mBottomRightCrop(1.0f, 1.0f) { updateColors(); } diff --git a/es-core/src/guis/GuiInputConfig.cpp b/es-core/src/guis/GuiInputConfig.cpp index 516d17c6e..aa9767459 100644 --- a/es-core/src/guis/GuiInputConfig.cpp +++ b/es-core/src/guis/GuiInputConfig.cpp @@ -181,7 +181,7 @@ GuiInputConfig::GuiInputConfig(Window* window, }; buttons.push_back( - std::make_shared(mWindow, "OK", "ok", [okFunction] { okFunction(); })); + std::make_shared(mWindow, "OK", "ok", [okFunction] { okFunction(); })); mButtonGrid = makeButtonGrid(mWindow, buttons); mGrid.setEntry(mButtonGrid, glm::ivec2{0, 6}, true, false); diff --git a/es-core/src/guis/GuiMsgBox.cpp b/es-core/src/guis/GuiMsgBox.cpp index 0e0a7fcb2..5e648eb26 100644 --- a/es-core/src/guis/GuiMsgBox.cpp +++ b/es-core/src/guis/GuiMsgBox.cpp @@ -15,22 +15,18 @@ #define HORIZONTAL_PADDING_PX 20.0f GuiMsgBox::GuiMsgBox(Window* window, - const HelpStyle& helpstyle, - const std::string& text, - const std::string& name1, - const std::function& func1, - const std::string& name2, - const std::function& func2, - const std::string& name3, - const std::function& func3, + const HelpStyle &helpstyle, + const std::string &text, + const std::string &name1, + const std::function &func1, + const std::string &name2, + const std::function &func2, + const std::string &name3, + const std::function &func3, bool disableBackButton, bool deleteOnButtonPress) - : GuiComponent(window) - , mBackground(window, ":/graphics/frame.svg") - , mGrid(window, glm::ivec2{1, 2}) - , mHelpStyle(helpstyle) - , mDisableBackButton(disableBackButton) - , mDeleteOnButtonPress(deleteOnButtonPress) + : GuiComponent(window), mBackground(window, ":/graphics/frame.svg"), mGrid(window, glm::ivec2{1, 2}), + mHelpStyle(helpstyle), mDisableBackButton(disableBackButton), mDeleteOnButtonPress(deleteOnButtonPress) { // Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent // regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference. diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp index b3f46a3d3..4edd8d95b 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp @@ -39,82 +39,68 @@ #include "utils/StringUtil.h" // clang-format off -std::vector> kbBaseUS{ - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "DEL"}, - {"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "DEL"}, - {"¡", "²", "³", "¤", "€", "¼", "½", "¾", "‘", "’", "¥", "×", "DEL"}, - {"¹", "", "", "£", "", "", "", "", "", "", "", "÷", "DEL"}, +std::vector> kbBaseUS{ + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "DEL"}, + {"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "DEL"}, + {"¡", "²", "³", "¤", "€", "¼", "½", "¾", "‘", "’", "¥", "×", "DEL"}, + {"¹", "", "", "£", "", "", "", "", "", "", "", "÷", "DEL"}, - {"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "OK"}, - {"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "OK"}, - {"ä", "å", "é", "®", "þ", "ü", "ú", "í", "ó", "ö", "«", "»", "OK"}, - {"Ä", "Å", "É", "", "Þ", "Ü", "Ú", "Í", "Ó", "Ö", "", "", "OK"}, + {"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "OK"}, + {"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "OK"}, + {"ä", "å", "é", "®", "þ", "ü", "ú", "í", "ó", "ö", "«", "»", "OK"}, + {"Ä", "Å", "É", "", "Þ", "Ü", "Ú", "Í", "Ó", "Ö", "", "", "OK"}, - {"a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\\", "-rowspan-"}, - {"A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "|", "-rowspan-"}, - {"á", "ß", "ð", "", "", "", "", "", "ø", "¶", "´", "¬", "-rowspan-"}, - {"Á", "§", "Ð", "", "", "", "", "", "Ø", "°", "¨", "¦", "-rowspan-"}, + {"a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\\", "-rowspan-"}, + {"A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "|", "-rowspan-"}, + {"á", "ß", "ð", "", "", "", "", "", "ø", "¶", "´", "¬", "-rowspan-"}, + {"Á", "§", "Ð", "", "", "", "", "", "Ø", "°", "¨", "¦", "-rowspan-"}, - {"`", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "ALT", "-colspan-"}, - {"~", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "ALT", "-colspan-"}, - {"", "æ", "", "©", "", "", "ñ", "µ", "ç", "", "¿", "ALT", "-colspan-"}, - {"", "Æ", "", "¢", "", "", "Ñ", "Μ", "Ç", "", "", "ALT", "-colspan-"}}; + {"`", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "ALT", "-colspan-"}, + {"~", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "ALT", "-colspan-"}, + {"", "æ", "", "©", "", "", "ñ", "µ", "ç", "", "¿", "ALT", "-colspan-"}, + {"", "Æ", "", "¢", "", "", "Ñ", "Μ", "Ç", "", "", "ALT", "-colspan-"}}; -std::vector> kbLastRowNormal{ - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; +std::vector> kbLastRowNormal{ + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; -std::vector> kbLastRowLoad{ - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; +std::vector> kbLastRowLoad{ + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; // clang-format on GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( - Window* window, - const HelpStyle& helpstyle, - const std::string& title, - const std::string& initValue, - const std::function& okCallback, - bool multiLine, - const std::string& acceptBtnHelpText, - const std::string& saveConfirmationText, - const std::string& infoString, - const std::string& defaultValue, - const std::string& loadBtnHelpText, - const std::string& clearBtnHelpText, - const std::string& cancelBtnHelpText) - : GuiComponent{window} - , mBackground{window, ":/graphics/frame.svg"} - , mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 8 : 6)}} - , mHelpStyle{helpstyle} - , mInitValue{initValue} - , mAcceptBtnHelpText{acceptBtnHelpText} - , mSaveConfirmationText{saveConfirmationText} - , mLoadBtnHelpText{loadBtnHelpText} - , mClearBtnHelpText{clearBtnHelpText} - , mCancelBtnHelpText{cancelBtnHelpText} - , mOkCallback{okCallback} - , mMultiLine{multiLine} - , mComplexMode{(infoString != "" && defaultValue != "")} - , mDeleteRepeat{false} - , mShift{false} - , mAlt{false} - , mDeleteRepeatTimer{0} - , mNavigationRepeatTimer{0} - , mNavigationRepeatDirX{0} - , mNavigationRepeatDirY{0} -{ + Window *window, + const HelpStyle &helpstyle, + const std::string &title, + const std::string &initValue, + const std::function &okCallback, + bool multiLine, + const std::string &acceptBtnHelpText, + const std::string &saveConfirmationText, + const std::string &infoString, + const std::string &defaultValue, + const std::string &loadBtnHelpText, + const std::string &clearBtnHelpText, + const std::string &cancelBtnHelpText) + : GuiComponent{window}, mBackground{window, ":/graphics/frame.svg"}, + mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 8 : 6)}}, mHelpStyle{helpstyle}, + mInitValue{initValue}, mAcceptBtnHelpText{acceptBtnHelpText}, mSaveConfirmationText{saveConfirmationText}, + mLoadBtnHelpText{loadBtnHelpText}, mClearBtnHelpText{clearBtnHelpText}, mCancelBtnHelpText{cancelBtnHelpText}, + mOkCallback{okCallback}, mMultiLine{multiLine}, mComplexMode{(infoString != "" && defaultValue != "")}, + mDeleteRepeat{false}, mShift{false}, mAlt{false}, mDeleteRepeatTimer{0}, mNavigationRepeatTimer{0}, + mNavigationRepeatDirX{0}, mNavigationRepeatDirY{0} { addChild(&mBackground); addChild(&mGrid); mTitle = std::make_shared(mWindow, Utils::String::toUpper(title), Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); - std::vector> kbLayout; + std::vector> kbLayout; // At the moment there is only the US keyboard layout available. kbLayout.insert(kbLayout.cend(), kbBaseUS.cbegin(), kbBaseUS.cend()); @@ -128,7 +114,7 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( mHorizontalKeyCount = static_cast(kbLayout[0].size()); mKeyboardGrid = std::make_shared( - mWindow, glm::ivec2(mHorizontalKeyCount, static_cast(kbLayout.size()) / 3)); + mWindow, glm::ivec2(mHorizontalKeyCount, static_cast(kbLayout.size()) / 3)); mText = std::make_shared(mWindow); mText->setValue(initValue); @@ -143,11 +129,11 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( if (mComplexMode) { mInfoString = std::make_shared( - mWindow, infoString, Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER); + mWindow, infoString, Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER); mGrid.setEntry(mInfoString, glm::ivec2{0, yPos}, false, true); mDefaultValue = std::make_shared( - mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); + mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); mGrid.setEntry(mDefaultValue, glm::ivec2{0, yPos + 1}, false, true); yPos += 2; } @@ -178,20 +164,17 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( upper = DELETE_SYMBOL; alted = DELETE_SYMBOL; altshifted = DELETE_SYMBOL; - } - else if (lower == "OK") { + } else if (lower == "OK") { lower = OK_SYMBOL; upper = OK_SYMBOL; alted = OK_SYMBOL; altshifted = OK_SYMBOL; - } - else if (lower == "SPACE") { + } else if (lower == "SPACE") { lower = " "; upper = " "; alted = " "; altshifted = " "; - } - else if (lower != "SHIFT" && lower.length() > 1) { + } else if (lower != "SHIFT" && lower.length() > 1) { lower = (lower.c_str()); upper = (upper.c_str()); alted = (alted.c_str()); @@ -200,21 +183,19 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( if (lower == "SHIFT") { mShiftButton = std::make_shared( - mWindow, (SHIFT_SYMBOL), ("SHIFT"), [this] { shiftKeys(); }, false, true); + mWindow, (SHIFT_SYMBOL), ("SHIFT"), [this] { shiftKeys(); }, false, true); button = mShiftButton; - } - else if (lower == "ALT") { + } else if (lower == "ALT") { mAltButton = std::make_shared( - mWindow, (ALT_SYMBOL), ("ALT"), [this] { altKeys(); }, false, true); + mWindow, (ALT_SYMBOL), ("ALT"), [this] { altKeys(); }, false, true); button = mAltButton; - } - else { + } else { button = makeButton(lower, upper, alted, altshifted); } button->setPadding( - glm::vec4(BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f, - BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f)); + glm::vec4(BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f, + BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f)); buttons.push_back(button); int colSpan = 1; @@ -252,14 +233,13 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( mText->setSize(0.0f, textHeight); // If attempting to navigate beyond the edge of the keyboard grid, then wrap around. - mGrid.setPastBoundaryCallback([this, kbLayout](InputConfig* config, Input input) -> bool { + mGrid.setPastBoundaryCallback([this, kbLayout](InputConfig *config, Input input) -> bool { if (config->isMappedLike("left", input)) { if (mGrid.getSelectedComponent() == mKeyboardGrid) { mKeyboardGrid->moveCursorTo(mHorizontalKeyCount - 1, -1, true); return true; } - } - else if (config->isMappedLike("right", input)) { + } else if (config->isMappedLike("right", input)) { if (mGrid.getSelectedComponent() == mKeyboardGrid) { mKeyboardGrid->moveCursorTo(0, -1); return true; @@ -279,8 +259,7 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( setPosition((static_cast(Renderer::getScreenWidth()) - mSize.x) / 2.0f, (static_cast(Renderer::getScreenHeight()) - mSize.y) / 2.0f); - } - else { + } else { if (mComplexMode) setSize(width, KEYBOARD_HEIGHT + mDefaultValue->getSize().y * 3.0f); else @@ -291,8 +270,7 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( } } -void GuiTextEditKeyboardPopup::onSizeChanged() -{ +void GuiTextEditKeyboardPopup::onSizeChanged() { mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); mText->setSize(mSize.x - KEYBOARD_PADDINGX - KEYBOARD_PADDINGX, mText->getSize().y); @@ -303,8 +281,7 @@ void GuiTextEditKeyboardPopup::onSizeChanged() mGrid.setRowHeightPerc(1, (mInfoString->getSize().y * 0.6f) / mSize.y); mGrid.setRowHeightPerc(2, (mDefaultValue->getSize().y * 1.6f) / mSize.y); mGrid.setRowHeightPerc(1, (mText->getSize().y * 1.0f) / mSize.y); - } - else if (mMultiLine) { + } else if (mMultiLine) { mGrid.setRowHeightPerc(1, (mText->getSize().y * 1.15f) / mSize.y); } @@ -319,8 +296,7 @@ void GuiTextEditKeyboardPopup::onSizeChanged() mKeyboardGrid->setPosition(KEYBOARD_PADDINGX, pos.y); } -bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) -{ +bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { // Enter/return key or numpad enter key accepts the changes. if (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && !mMultiLine && input.value && (input.id == SDLK_RETURN || input.id == SDLK_KP_ENTER)) { @@ -328,7 +304,7 @@ bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) delete this; return true; } - // Dito for the A button if using a controller. + // Dito for the A button if using a controller. else if (config->getDeviceId() != DEVICE_KEYBOARD && mText->isEditing() && config->isMappedTo("a", input) && input.value) { this->mOkCallback(mText->getValue()); @@ -354,19 +330,18 @@ bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) if (mText->getValue() != mInitValue) { // Changes were made, ask if the user wants to save them. mWindow->pushGui(new GuiMsgBox( - mWindow, mHelpStyle, mSaveConfirmationText, "YES", - [this] { - this->mOkCallback(mText->getValue()); - delete this; - return true; - }, - "NO", - [this] { - delete this; - return true; - })); - } - else { + mWindow, mHelpStyle, mSaveConfirmationText, "YES", + [this] { + this->mOkCallback(mText->getValue()); + delete this; + return true; + }, + "NO", + [this] { + delete this; + return true; + })); + } else { delete this; return true; } @@ -399,8 +374,7 @@ bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) if (!editing) mText->stopEditing(); - } - else { + } else { mDeleteRepeat = false; } return true; @@ -426,8 +400,7 @@ bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) if (input.value) { mNavigationRepeatDirX = -1; mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); - } - else { + } else { mNavigationRepeatDirX = 0; } } @@ -436,8 +409,7 @@ bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) if (input.value) { mNavigationRepeatDirX = 1; mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); - } - else { + } else { mNavigationRepeatDirX = 0; } } @@ -446,8 +418,7 @@ bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) if (input.value) { mNavigationRepeatDirY = -1; mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); - } - else { + } else { mNavigationRepeatDirY = 0; } } @@ -456,8 +427,7 @@ bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) if (input.value) { mNavigationRepeatDirY = 1; mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); - } - else { + } else { mNavigationRepeatDirY = 0; } } @@ -468,22 +438,19 @@ bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) return false; } -void GuiTextEditKeyboardPopup::update(int deltaTime) -{ +void GuiTextEditKeyboardPopup::update(int deltaTime) { updateNavigationRepeat(deltaTime); updateDeleteRepeat(deltaTime); GuiComponent::update(deltaTime); } -std::vector GuiTextEditKeyboardPopup::getHelpPrompts() -{ +std::vector GuiTextEditKeyboardPopup::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); if (!mText->isEditing()) { prompts.push_back(HelpPrompt("lt", "shift")); prompts.push_back(HelpPrompt("rt", "alt")); - } - else { + } else { prompts.push_back(HelpPrompt("a", mAcceptBtnHelpText)); } @@ -513,8 +480,7 @@ std::vector GuiTextEditKeyboardPopup::getHelpPrompts() return prompts; } -void GuiTextEditKeyboardPopup::updateDeleteRepeat(int deltaTime) -{ +void GuiTextEditKeyboardPopup::updateDeleteRepeat(int deltaTime) { if (!mDeleteRepeat) return; @@ -534,8 +500,7 @@ void GuiTextEditKeyboardPopup::updateDeleteRepeat(int deltaTime) } } -void GuiTextEditKeyboardPopup::updateNavigationRepeat(int deltaTime) -{ +void GuiTextEditKeyboardPopup::updateNavigationRepeat(int deltaTime) { if (mNavigationRepeatDirX == 0 && mNavigationRepeatDirY == 0) return; @@ -562,15 +527,13 @@ void GuiTextEditKeyboardPopup::updateNavigationRepeat(int deltaTime) } } -void GuiTextEditKeyboardPopup::shiftKeys() -{ +void GuiTextEditKeyboardPopup::shiftKeys() { mShift = !mShift; if (mShift) { mShiftButton->setFlatColorFocused(0xFF2222FF); mShiftButton->setFlatColorUnfocused(0xFF2222FF); - } - else { + } else { mShiftButton->setFlatColorFocused(0x878787FF); mShiftButton->setFlatColorUnfocused(0x60606025); } @@ -584,10 +547,9 @@ void GuiTextEditKeyboardPopup::shiftKeys() if (mAlt) { altKeys(); altKeys(); - } - else { - for (auto& kb : mKeyboardButtons) { - const std::string& text = mShift ? kb.shiftedKey : kb.key; + } else { + for (auto &kb: mKeyboardButtons) { + const std::string &text = mShift ? kb.shiftedKey : kb.key; auto sz = kb.button->getSize(); kb.button->setText(text, text, false); kb.button->setSize(sz); @@ -595,15 +557,13 @@ void GuiTextEditKeyboardPopup::shiftKeys() } } -void GuiTextEditKeyboardPopup::altKeys() -{ +void GuiTextEditKeyboardPopup::altKeys() { mAlt = !mAlt; if (mAlt) { mAltButton->setFlatColorFocused(0xFF2222FF); mAltButton->setFlatColorUnfocused(0xFF2222FF); - } - else { + } else { mAltButton->setFlatColorFocused(0x878787FF); mAltButton->setFlatColorUnfocused(0x60606025); } @@ -617,10 +577,9 @@ void GuiTextEditKeyboardPopup::altKeys() if (mShift) { shiftKeys(); shiftKeys(); - } - else { - for (auto& kb : mKeyboardButtons) { - const std::string& text = mAlt ? kb.altedKey : kb.key; + } else { + for (auto &kb: mKeyboardButtons) { + const std::string &text = mAlt ? kb.altedKey : kb.key; auto sz = kb.button->getSize(); kb.button->setText(text, text, false); kb.button->setSize(sz); @@ -628,10 +587,9 @@ void GuiTextEditKeyboardPopup::altKeys() } } -void GuiTextEditKeyboardPopup::altShiftKeys() -{ - for (auto& kb : mKeyboardButtons) { - const std::string& text = kb.altshiftedKey; +void GuiTextEditKeyboardPopup::altShiftKeys() { + for (auto &kb: mKeyboardButtons) { + const std::string &text = kb.altshiftedKey; auto sz = kb.button->getSize(); kb.button->setText(text, text, false); kb.button->setSize(sz); @@ -639,62 +597,56 @@ void GuiTextEditKeyboardPopup::altShiftKeys() } std::shared_ptr GuiTextEditKeyboardPopup::makeButton( - const std::string& key, - const std::string& shiftedKey, - const std::string& altedKey, - const std::string& altshiftedKey) -{ + const std::string &key, + const std::string &shiftedKey, + const std::string &altedKey, + const std::string &altshiftedKey) { std::shared_ptr button = std::make_shared( - mWindow, key, key, - [this, key, shiftedKey, altedKey, altshiftedKey] { - if (key == (OK_SYMBOL) || key.find("OK") != std::string::npos) { - mOkCallback(mText->getValue()); - delete this; - return; - } - else if (key == (DELETE_SYMBOL) || key == "DEL") { + mWindow, key, key, + [this, key, shiftedKey, altedKey, altshiftedKey] { + if (key == (OK_SYMBOL) || key.find("OK") != std::string::npos) { + mOkCallback(mText->getValue()); + delete this; + return; + } else if (key == (DELETE_SYMBOL) || key == "DEL") { + mText->startEditing(); + mText->textInput("\b"); + mText->stopEditing(); + return; + } else if (key == "SPACE" || key == " ") { + mText->startEditing(); + mText->textInput(" "); + mText->stopEditing(); + return; + } else if (key == "LOAD") { + mText->setValue(mDefaultValue->getValue()); + mText->setCursor(mDefaultValue->getValue().size()); + return; + } else if (key == "CLEAR") { + mText->setValue(""); + return; + } else if (key == "CANCEL") { + delete this; + return; + } + + if (mAlt && altedKey.empty()) + return; + mText->startEditing(); - mText->textInput("\b"); + + if (mShift && mAlt) + mText->textInput(altshiftedKey.c_str()); + else if (mAlt) + mText->textInput(altedKey.c_str()); + else if (mShift) + mText->textInput(shiftedKey.c_str()); + else + mText->textInput(key.c_str()); + mText->stopEditing(); - return; - } - else if (key == "SPACE" || key == " ") { - mText->startEditing(); - mText->textInput(" "); - mText->stopEditing(); - return; - } - else if (key == "LOAD") { - mText->setValue(mDefaultValue->getValue()); - mText->setCursor(mDefaultValue->getValue().size()); - return; - } - else if (key == "CLEAR") { - mText->setValue(""); - return; - } - else if (key == "CANCEL") { - delete this; - return; - } - - if (mAlt && altedKey.empty()) - return; - - mText->startEditing(); - - if (mShift && mAlt) - mText->textInput(altshiftedKey.c_str()); - else if (mAlt) - mText->textInput(altedKey.c_str()); - else if (mShift) - mText->textInput(shiftedKey.c_str()); - else - mText->textInput(key.c_str()); - - mText->stopEditing(); - }, - false, true); + }, + false, true); KeyboardButton kb(button, key, shiftedKey, altedKey, altshiftedKey); mKeyboardButtons.push_back(kb); diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.h b/es-core/src/guis/GuiTextEditKeyboardPopup.h index b8660fdf0..4104cb6cb 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.h +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.h @@ -15,62 +15,64 @@ #include "components/ComponentGrid.h" #include "components/TextEditComponent.h" -class GuiTextEditKeyboardPopup : public GuiComponent -{ +class GuiTextEditKeyboardPopup : public GuiComponent { public: - GuiTextEditKeyboardPopup(Window* window, - const HelpStyle& helpstyle, - const std::string& title, - const std::string& initValue, - const std::function& okCallback, + GuiTextEditKeyboardPopup(Window *window, + const HelpStyle &helpstyle, + const std::string &title, + const std::string &initValue, + const std::function &okCallback, bool multiLine, - const std::string& acceptBtnHelpText = "OK", - const std::string& saveConfirmationText = "SAVE CHANGES?", - const std::string& infoString = "", - const std::string& defaultValue = "", - const std::string& loadBtnHelpText = "LOAD DEFAULT", - const std::string& clearBtnHelpText = "CLEAR", - const std::string& cancelBtnHelpText = "DISCARD CHANGES"); + const std::string &acceptBtnHelpText = "OK", + const std::string &saveConfirmationText = "SAVE CHANGES?", + const std::string &infoString = "", + const std::string &defaultValue = "", + const std::string &loadBtnHelpText = "LOAD DEFAULT", + const std::string &clearBtnHelpText = "CLEAR", + const std::string &cancelBtnHelpText = "DISCARD CHANGES"); void onSizeChanged() override; - bool input(InputConfig* config, Input input) override; + + bool input(InputConfig *config, Input input) override; + void update(int deltaTime) override; std::vector getHelpPrompts() override; + HelpStyle getHelpStyle() override { return mHelpStyle; } private: - class KeyboardButton - { + class KeyboardButton { public: std::shared_ptr button; const std::string key; const std::string shiftedKey; const std::string altedKey; const std::string altshiftedKey; + KeyboardButton(const std::shared_ptr b, - const std::string& k, - const std::string& sk, - const std::string& ak, - const std::string& ask) - : button{b} - , key{k} - , shiftedKey{sk} - , altedKey{ak} - , altshiftedKey{ask} {}; + const std::string &k, + const std::string &sk, + const std::string &ak, + const std::string &ask) + : button{b}, key{k}, shiftedKey{sk}, altedKey{ak}, altshiftedKey{ask} {}; }; void updateDeleteRepeat(int deltaTime); + void updateNavigationRepeat(int deltaTime); void shiftKeys(); + void altKeys(); + void altShiftKeys(); - std::shared_ptr makeButton(const std::string& key, - const std::string& shiftedKey, - const std::string& altedKey, - const std::string& altshiftedKey); + std::shared_ptr makeButton(const std::string &key, + const std::string &shiftedKey, + const std::string &altedKey, + const std::string &altshiftedKey); + std::vector mKeyboardButtons; std::shared_ptr mShiftButton; @@ -93,7 +95,7 @@ private: std::string mClearBtnHelpText; std::string mCancelBtnHelpText; - std::function mOkCallback; + std::function mOkCallback; bool mMultiLine; bool mComplexMode; diff --git a/es-core/src/guis/GuiTextEditPopup.cpp b/es-core/src/guis/GuiTextEditPopup.cpp index 132d81056..898f4a4a6 100644 --- a/es-core/src/guis/GuiTextEditPopup.cpp +++ b/es-core/src/guis/GuiTextEditPopup.cpp @@ -15,35 +15,25 @@ #include "components/MenuComponent.h" #include "guis/GuiMsgBox.h" -GuiTextEditPopup::GuiTextEditPopup(Window* window, - const HelpStyle& helpstyle, - const std::string& title, - const std::string& initValue, - const std::function& okCallback, +GuiTextEditPopup::GuiTextEditPopup(Window *window, + const HelpStyle &helpstyle, + const std::string &title, + const std::string &initValue, + const std::function &okCallback, bool multiLine, - const std::string& acceptBtnText, - const std::string& saveConfirmationText, - const std::string& infoString, - const std::string& defaultValue, - const std::string& loadBtnHelpText, - const std::string& clearBtnHelpText, - const std::string& cancelBtnHelpText) - : GuiComponent{window} - , mBackground{window, ":/graphics/frame.svg"} - , mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 5 : 3)}} - , mHelpStyle{helpstyle} - , mInitValue{initValue} - , mAcceptBtnText{acceptBtnText} - , mSaveConfirmationText{saveConfirmationText} - , mLoadBtnHelpText{loadBtnHelpText} - , mClearBtnHelpText{clearBtnHelpText} - , mCancelBtnHelpText{cancelBtnHelpText} - , mOkCallback{okCallback} - , mMultiLine{multiLine} - , mComplexMode{(infoString != "" && defaultValue != "")} - , mDeleteRepeat{false} - , mDeleteRepeatTimer{0} -{ + const std::string &acceptBtnText, + const std::string &saveConfirmationText, + const std::string &infoString, + const std::string &defaultValue, + const std::string &loadBtnHelpText, + const std::string &clearBtnHelpText, + const std::string &cancelBtnHelpText) + : GuiComponent{window}, mBackground{window, ":/graphics/frame.svg"}, + mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 5 : 3)}}, mHelpStyle{helpstyle}, + mInitValue{initValue}, mAcceptBtnText{acceptBtnText}, mSaveConfirmationText{saveConfirmationText}, + mLoadBtnHelpText{loadBtnHelpText}, mClearBtnHelpText{clearBtnHelpText}, mCancelBtnHelpText{cancelBtnHelpText}, + mOkCallback{okCallback}, mMultiLine{multiLine}, mComplexMode{(infoString != "" && defaultValue != "")}, + mDeleteRepeat{false}, mDeleteRepeatTimer{0} { addChild(&mBackground); addChild(&mGrid); @@ -52,9 +42,9 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window, if (mComplexMode) { mInfoString = std::make_shared( - mWindow, infoString, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); + mWindow, infoString, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); mDefaultValue = std::make_shared( - mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); + mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); } mText = std::make_shared(mWindow); @@ -68,11 +58,11 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window, })); if (mComplexMode) { buttons.push_back(std::make_shared( - mWindow, "load", loadBtnHelpText, [this, defaultValue] { - mText->setValue(defaultValue); - mText->setCursor(0); - mText->setCursor(defaultValue.size()); - })); + mWindow, "load", loadBtnHelpText, [this, defaultValue] { + mText->setValue(defaultValue); + mText->setCursor(0); + mText->setCursor(defaultValue.size()); + })); } buttons.push_back(std::make_shared(mWindow, "clear", clearBtnHelpText, @@ -109,22 +99,21 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window, if (mComplexMode) { float infoWidth = - glm::clamp(0.70f * aspectValue, 0.34f, 0.85f) * Renderer::getScreenWidth(); + glm::clamp(0.70f * aspectValue, 0.34f, 0.85f) * Renderer::getScreenWidth(); float windowWidth = - glm::clamp(0.75f * aspectValue, 0.40f, 0.90f) * Renderer::getScreenWidth(); + glm::clamp(0.75f * aspectValue, 0.40f, 0.90f) * Renderer::getScreenWidth(); mDefaultValue->setSize(infoWidth, mDefaultValue->getFont()->getHeight()); setSize(windowWidth, mTitle->getFont()->getHeight() + textHeight + - mButtonGrid->getSize().y + mButtonGrid->getSize().y * 1.85f); + mButtonGrid->getSize().y + mButtonGrid->getSize().y * 1.85f); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); - } - else { + } else { float width = glm::clamp(0.54f * aspectValue, 0.20f, 0.70f) * Renderer::getScreenWidth(); setSize(width, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y + - mButtonGrid->getSize().y / 2.0f); + mButtonGrid->getSize().y / 2.0f); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); } @@ -135,8 +124,7 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window, mText->startEditing(); } -void GuiTextEditPopup::onSizeChanged() -{ +void GuiTextEditPopup::onSizeChanged() { mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); mText->setSize(mSize.x - 40.0f * Renderer::getScreenHeightModifier(), mText->getSize().y); @@ -150,8 +138,7 @@ void GuiTextEditPopup::onSizeChanged() mGrid.setSize(mSize); } -bool GuiTextEditPopup::input(InputConfig* config, Input input) -{ +bool GuiTextEditPopup::input(InputConfig *config, Input input) { // Enter key (main key or via numpad) accepts the changes. if (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && !mMultiLine && input.value && (input.id == SDLK_RETURN || input.id == SDLK_KP_ENTER)) { @@ -159,7 +146,7 @@ bool GuiTextEditPopup::input(InputConfig* config, Input input) delete this; return true; } - // Dito for the A button if using a controller. + // Dito for the A button if using a controller. else if (config->getDeviceId() != DEVICE_KEYBOARD && mText->isEditing() && config->isMappedTo("a", input) && input.value) { this->mOkCallback(mText->getValue()); @@ -179,19 +166,18 @@ bool GuiTextEditPopup::input(InputConfig* config, Input input) if (mText->getValue() != mInitValue) { // Changes were made, ask if the user wants to save them. mWindow->pushGui(new GuiMsgBox( - mWindow, mHelpStyle, mSaveConfirmationText, "YES", - [this] { - this->mOkCallback(mText->getValue()); - delete this; - return true; - }, - "NO", - [this] { - delete this; - return true; - })); - } - else { + mWindow, mHelpStyle, mSaveConfirmationText, "YES", + [this] { + this->mOkCallback(mText->getValue()); + delete this; + return true; + }, + "NO", + [this] { + delete this; + return true; + })); + } else { delete this; return true; } @@ -216,8 +202,7 @@ bool GuiTextEditPopup::input(InputConfig* config, Input input) if (!editing) mText->stopEditing(); - } - else { + } else { mDeleteRepeat = false; } return true; @@ -243,14 +228,12 @@ bool GuiTextEditPopup::input(InputConfig* config, Input input) return false; } -void GuiTextEditPopup::update(int deltaTime) -{ +void GuiTextEditPopup::update(int deltaTime) { updateDeleteRepeat(deltaTime); GuiComponent::update(deltaTime); } -std::vector GuiTextEditPopup::getHelpPrompts() -{ +std::vector GuiTextEditPopup::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); if (mText->isEditing()) @@ -262,8 +245,7 @@ std::vector GuiTextEditPopup::getHelpPrompts() return prompts; } -void GuiTextEditPopup::updateDeleteRepeat(int deltaTime) -{ +void GuiTextEditPopup::updateDeleteRepeat(int deltaTime) { if (!mDeleteRepeat) return; diff --git a/es-core/src/guis/GuiTextEditPopup.h b/es-core/src/guis/GuiTextEditPopup.h index a77623704..4784cecf7 100644 --- a/es-core/src/guis/GuiTextEditPopup.h +++ b/es-core/src/guis/GuiTextEditPopup.h @@ -15,28 +15,30 @@ #include "components/ComponentGrid.h" #include "components/TextEditComponent.h" -class GuiTextEditPopup : public GuiComponent -{ +class GuiTextEditPopup : public GuiComponent { public: - GuiTextEditPopup(Window* window, - const HelpStyle& helpstyle, - const std::string& title, - const std::string& initValue, - const std::function& okCallback, + GuiTextEditPopup(Window *window, + const HelpStyle &helpstyle, + const std::string &title, + const std::string &initValue, + const std::function &okCallback, bool multiLine, - const std::string& acceptBtnText = "OK", - const std::string& saveConfirmationText = "SAVE CHANGES?", - const std::string& infoString = "", - const std::string& defaultValue = "", - const std::string& loadBtnHelpText = "LOAD DEFAULT", - const std::string& clearBtnHelpText = "CLEAR", - const std::string& cancelBtnHelpText = "DISCARD CHANGES"); + const std::string &acceptBtnText = "OK", + const std::string &saveConfirmationText = "SAVE CHANGES?", + const std::string &infoString = "", + const std::string &defaultValue = "", + const std::string &loadBtnHelpText = "LOAD DEFAULT", + const std::string &clearBtnHelpText = "CLEAR", + const std::string &cancelBtnHelpText = "DISCARD CHANGES"); void onSizeChanged() override; - bool input(InputConfig* config, Input input) override; + + bool input(InputConfig *config, Input input) override; + void update(int deltaTime) override; std::vector getHelpPrompts() override; + HelpStyle getHelpStyle() override { return mHelpStyle; } private: @@ -59,7 +61,7 @@ private: std::string mClearBtnHelpText; std::string mCancelBtnHelpText; - std::function mOkCallback; + std::function mOkCallback; bool mMultiLine; bool mComplexMode; diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 5cdb52e53..21c15346f 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -562,18 +562,18 @@ float Font::getNewlineStartOffset(const std::string& text, endChar = static_cast(text.find('\n', charStart)); return (xLen - sizeText(text.substr(charStart, static_cast(endChar) != std::string::npos ? - endChar - charStart : - endChar)) - .x) / + endChar - charStart : + endChar)) + .x) / 2.0f; } case ALIGN_RIGHT: { int endChar = static_cast(text.find('\n', charStart)); return xLen - (sizeText(text.substr(charStart, static_cast(endChar) != std::string::npos ? - endChar - charStart : - endChar)) - .x); + endChar - charStart : + endChar)) + .x); } default: return 0; diff --git a/resources/systems/macos/es_systems.xml b/resources/systems/macos/es_systems.xml index 0457f49ff..2d6da89f1 100644 --- a/resources/systems/macos/es_systems.xml +++ b/resources/systems/macos/es_systems.xml @@ -98,10 +98,12 @@ .cmd .CMD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% + arcade arcade
@@ -218,10 +220,15 @@ c64 Commodore 64 %ROMPATH%/c64 - .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64sc_libretro.dylib %ROM% + .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 + .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ + .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64sc_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_xscpu64_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_xscpu64_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x128_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/frodo_libretro.dylib %ROM% c64 @@ -354,11 +361,20 @@ FinalBurn Alpha %ROMPATH%/fba .iso .ISO .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_neogeo_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps2_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps3_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% + + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%/fbalpha2012_neogeo_libretro.dylib %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.dylib + %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps2_libretro.dylib + %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps3_libretro.dylib + %ROM% + arcade fba @@ -438,9 +454,14 @@ genesis Sega Genesis %ROMPATH%/genesis - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx + .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% + + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/blastem_libretro.dylib %ROM% genesis @@ -505,12 +526,14 @@ Multiple Arcade Machine Emulator %ROMPATH%/mame .cmd .CMD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% + arcade mame @@ -536,9 +559,14 @@ mastersystem Sega Master System %ROMPATH%/mastersystem - .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md + .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% + + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/smsplus_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/gearsystem_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.dylib %ROM% @@ -567,9 +595,14 @@ megadrive Sega Mega Drive %ROMPATH%/megadrive - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx + .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% + + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/blastem_libretro.dylib %ROM% megadrive @@ -818,9 +851,13 @@ pcengine NEC PC Engine %ROMPATH%/pcengine - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib + %ROM% + pcengine pcengine @@ -828,9 +865,13 @@ pcenginecd NEC PC Engine CD %ROMPATH%/pcenginecd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib + %ROM% + pcenginecd pcenginecd @@ -910,10 +951,14 @@ psx Sony PlayStation %ROMPATH%/psx - .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP + .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U + .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_hw_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/pcsx_rearmed_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_hw_libretro.dylib %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/pcsx_rearmed_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/duckstation_libretro.dylib %ROM% psx psx @@ -1021,11 +1066,15 @@ snes Nintendo SNES (Super Nintendo) %ROMPATH%/snes - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC + .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen-s_libretro.dylib %ROM% snes snes @@ -1034,11 +1083,15 @@ snesna Nintendo SNES (Super Nintendo) %ROMPATH%/snesna - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC + .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen-s_libretro.dylib %ROM% snes snesna @@ -1093,7 +1146,9 @@ NEC SuperGrafx %ROMPATH%/supergrafx .pce .PCE .sgx .SGX .cue .CUE .ccd .CCD .chd .CHD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supergrafx_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supergrafx_libretro.dylib + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% supergrafx supergrafx @@ -1120,9 +1175,13 @@ tg16 NEC TurboGrafx-16 %ROMPATH%/tg16 - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib + %ROM% + pcengine tg16 @@ -1130,9 +1189,13 @@ tg-cd NEC TurboGrafx-CD %ROMPATH%/tg-cd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib + %ROM% + tg-cd diff --git a/resources/systems/unix/es_systems.xml b/resources/systems/unix/es_systems.xml index 83c0f653a..2b894b01c 100644 --- a/resources/systems/unix/es_systems.xml +++ b/resources/systems/unix/es_systems.xml @@ -15,7 +15,8 @@ Nintendo 64DD %ROMPATH%/64dd .n64 .N64 .v64 .V64 .z64 .Z64 .bin .BIN .u1 .U1 .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mupen64plus_next_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mupen64plus_next_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/parallel_n64_libretro.so %ROM% n64 64dd @@ -99,7 +100,8 @@ .cmd .CMD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.so %ROM% @@ -219,10 +221,15 @@ c64 Commodore 64 %ROMPATH%/c64 - .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64sc_libretro.so %ROM% + .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 + .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ + .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64sc_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_xscpu64_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_xscpu64_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x128_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/frodo_libretro.so %ROM% c64 @@ -356,10 +363,18 @@ %ROMPATH%/fba .iso .ISO .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_neogeo_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps2_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps3_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_neogeo_libretro.so + %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.so + %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps2_libretro.so + %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps3_libretro.so + %ROM% + arcade fba @@ -439,9 +454,14 @@ genesis Sega Genesis %ROMPATH%/genesis - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx + .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/blastem_libretro.so %ROM% genesis @@ -506,7 +526,8 @@ Multiple Arcade Machine Emulator %ROMPATH%/mame .cmd .CMD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.so %ROM% @@ -537,9 +558,14 @@ mastersystem Sega Master System %ROMPATH%/mastersystem - .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md + .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/smsplus_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/gearsystem_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.so %ROM% @@ -568,9 +594,14 @@ megadrive Sega Mega Drive %ROMPATH%/megadrive - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx + .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/blastem_libretro.so %ROM% megadrive @@ -662,7 +693,8 @@ Nintendo 64 %ROMPATH%/n64 .n64 .N64 .v64 .V64 .z64 .Z64 .bin .BIN .u1 .U1 .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mupen64plus_next_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mupen64plus_next_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/parallel_n64_libretro.so %ROM% n64 n64 @@ -820,9 +852,12 @@ pcengine NEC PC Engine %ROMPATH%/pcengine - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% + pcengine pcengine @@ -830,9 +865,12 @@ pcenginecd NEC PC Engine CD %ROMPATH%/pcenginecd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% + pcenginecd pcenginecd @@ -912,9 +950,12 @@ psx Sony PlayStation %ROMPATH%/psx - .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP + .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U + .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_hw_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_hw_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/pcsx_rearmed_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/duckstation_libretro.so %ROM% psx @@ -1023,12 +1064,18 @@ snes Nintendo SNES (Super Nintendo) %ROMPATH%/snes - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC + .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supafaust_libretro.so %ROM% + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.so %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supafaust_libretro.so + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen-s_libretro.so %ROM% snes snes @@ -1037,12 +1084,18 @@ snesna Nintendo SNES (Super Nintendo) %ROMPATH%/snesna - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC + .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supafaust_libretro.so %ROM% + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.so %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supafaust_libretro.so + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen-s_libretro.so %ROM% snes snesna @@ -1097,7 +1150,9 @@ NEC SuperGrafx %ROMPATH%/supergrafx .pce .PCE .sgx .SGX .cue .CUE .ccd .CCD .chd .CHD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supergrafx_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supergrafx_libretro.so + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% supergrafx supergrafx @@ -1124,9 +1179,12 @@ tg16 NEC TurboGrafx-16 %ROMPATH%/tg16 - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% + pcengine tg16 @@ -1134,9 +1192,12 @@ tg-cd NEC TurboGrafx-CD %ROMPATH%/tg-cd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% + pcenginecd tg-cd diff --git a/resources/systems/windows/es_systems.xml b/resources/systems/windows/es_systems.xml index 62843d550..6ed187456 100644 --- a/resources/systems/windows/es_systems.xml +++ b/resources/systems/windows/es_systems.xml @@ -15,7 +15,8 @@ Nintendo 64DD %ROMPATH%\64dd .n64 .N64 .v64 .V64 .z64 .Z64 .bin .BIN .u1 .U1 .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mupen64plus_next_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mupen64plus_next_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\parallel_n64_libretro.dll %ROM% n64 64dd @@ -99,7 +100,8 @@ .cmd .CMD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2000_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbneo_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_libretro.dll %ROM% @@ -219,10 +221,15 @@ c64 Commodore 64 %ROMPATH%\c64 - .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_x64sc_libretro.dll %ROM% + .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 + .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ + .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_x64sc_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_x64_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_xscpu64_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_xscpu64_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_x128_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\frodo_libretro.dll %ROM% c64 @@ -356,10 +363,18 @@ %ROMPATH%\fba .iso .ISO .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_neogeo_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps1_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps2_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps3_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_neogeo_libretro.dll + %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps1_libretro.dll + %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps2_libretro.dll + %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps3_libretro.dll + %ROM% + arcade fba @@ -439,9 +454,14 @@ genesis Sega Genesis %ROMPATH%\genesis - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx + .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\picodrive_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\blastem_libretro.dll %ROM% genesis @@ -506,7 +526,8 @@ Multiple Arcade Machine Emulator %ROMPATH%\mame .cmd .CMD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2000_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame_libretro.dll %ROM% @@ -537,9 +558,14 @@ mastersystem Sega Master System %ROMPATH%\mastersystem - .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md + .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\smsplus_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\gearsystem_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\picodrive_libretro.dll %ROM% @@ -568,9 +594,14 @@ megadrive Sega Mega Drive %ROMPATH%\megadrive - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll %ROM% + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx + .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\picodrive_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\blastem_libretro.dll %ROM% megadrive @@ -662,7 +693,8 @@ Nintendo 64 %ROMPATH%\n64 .n64 .N64 .v64 .V64 .z64 .Z64 .bin .BIN .u1 .U1 .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mupen64plus_next_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mupen64plus_next_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\parallel_n64_libretro.dll %ROM% n64 n64 @@ -820,9 +852,12 @@ pcengine NEC PC Engine %ROMPATH%\pcengine - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% + pcengine pcengine @@ -830,9 +865,12 @@ pcenginecd NEC PC Engine CD %ROMPATH%\pcenginecd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% + pcenginecd pcenginecd @@ -912,9 +950,12 @@ psx Sony PlayStation %ROMPATH%\psx - .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP + .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U + .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_psx_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_psx_hw_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_psx_hw_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\pcsx_rearmed_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\duckstation_libretro.dll %ROM% psx @@ -1023,12 +1064,18 @@ snes Nintendo SNES (Super Nintendo) %ROMPATH%\snes - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC + .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\snes9x_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\snes9x2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\bsnes_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\bsnes_mercury_accuracy_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supafaust_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%\bsnes_mercury_accuracy_libretro.dll %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supafaust_libretro.dll + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mesen-s_libretro.dll %ROM% snes snes @@ -1037,12 +1084,18 @@ snesna Nintendo SNES (Super Nintendo) %ROMPATH%\snesna - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC + .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\snes9x_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\snes9x2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\bsnes_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\bsnes_mercury_accuracy_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supafaust_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L + %CORE_RETROARCH%\bsnes_mercury_accuracy_libretro.dll %ROM% + + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supafaust_libretro.dll + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mesen-s_libretro.dll %ROM% snes snesna @@ -1097,7 +1150,9 @@ NEC SuperGrafx %ROMPATH%\supergrafx .pce .PCE .sgx .SGX .cue .CUE .ccd .CCD .chd .CHD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supergrafx_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supergrafx_libretro.dll + %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% supergrafx supergrafx @@ -1124,9 +1179,12 @@ tg16 NEC TurboGrafx-16 %ROMPATH%\tg16 - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% + pcengine tg16 @@ -1134,9 +1192,12 @@ tg-cd NEC TurboGrafx-CD %ROMPATH%\tg-cd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC + .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% + pcenginecd tg-cd diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index a0fc8861a..00c48fb13 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -238,7 +238,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.8125 0.65 - 0 0 + 0.5 0.5 row From 7820a94442cc22d775301d29939e494c735a563d Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Fri, 24 Sep 2021 00:26:41 +0200 Subject: [PATCH 010/128] Implement origin. --- es-core/src/components/FlexboxComponent.cpp | 34 +++++++++++++++------ themes/rbsimple-DE/theme.xml | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 37e26a952..8569ea358 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -66,7 +66,7 @@ void FlexboxComponent::onSizeChanged() { void FlexboxComponent::computeLayout() { - // Start placing items in the top-left; + // Start placing items in the top-left. float anchorX = 0; float anchorY = 0; float anchorOriginX = 0; @@ -93,17 +93,27 @@ void FlexboxComponent::computeLayout() maxItemSize = {std::max(maxItemSize.x, newSize.x), std::max(maxItemSize.y, newSize.y)}; } - // Pre-compute layout parameters; + // Pre-compute layout parameters. int n = mChildren.size(); - int nLines = std::max(1, (int)std::ceil(n / std::max(1, (int)mItemsPerLine))); + int nLines = std::max(1, (int) std::ceil(n / std::max(1, (int) mItemsPerLine))); float lineWidth = - (mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); + (mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); float anchorXStart = anchorX; float anchorYStart = anchorY; + // Compute total container size. + glm::vec2 totalSize = {mItemMargin.x, mItemMargin.y}; + if (mDirection == "row") { + totalSize.x += (mItemMargin.x + mItemWidth) * mItemsPerLine; + totalSize.y += (mItemMargin.y + maxItemSize.y) * nLines; + } else { + totalSize.x += (mItemMargin.x + mItemWidth) * nLines; + totalSize.y += (mItemMargin.y + maxItemSize.y) * mItemsPerLine; + } + // Iterate through the children. for (int i = 0; i < n; i++) { - GuiComponent* child = mChildren[i]; + GuiComponent *child = mChildren[i]; auto size = child->getSize(); // Top-left anchor position. @@ -118,20 +128,24 @@ void FlexboxComponent::computeLayout() if (mAlign == ITEM_ALIGN_END) { x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) : 0; - } - else if (mAlign == ITEM_ALIGN_CENTER) { + } else if (mAlign == ITEM_ALIGN_CENTER) { x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; - } - else if (mAlign == ITEM_ALIGN_STRETCH && mDirection == "row") { + } else if (mAlign == ITEM_ALIGN_STRETCH && mDirection == "row") { child->setSize(child->getSize().x, maxItemSize.y); } + // Apply origin. + if (mOrigin.x > 0 && mOrigin.x <= 1) + x -= mOrigin.x * totalSize.x; + if (mOrigin.y > 0 && mOrigin.y <= 1) + y -= mOrigin.y * totalSize.y; + // Store final item position. child->setPosition(getPosition().x + x, getPosition().y + y); // Translate anchor. - if ((i + 1) % std::max(1, (int)mItemsPerLine) != 0) { + if ((i + 1) % std::max(1, (int) mItemsPerLine) != 0) { // Translate on same line. anchorX += (size.x + mItemMargin.x) * directionLine.x; anchorY += (size.y + mItemMargin.y) * directionLine.y; diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 00c48fb13..a0fc8861a 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -238,7 +238,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.8125 0.65 - 0.5 0.5 + 0 0 row From c08bdda008df953d36f0d9e9cc675f32b360869b Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Fri, 24 Sep 2021 00:40:55 +0200 Subject: [PATCH 011/128] fix badges in detailed view. --- .../views/gamelist/DetailedGameListView.cpp | 43 +++++++++++++------ .../src/views/gamelist/VideoGameListView.cpp | 1 - 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index f2aac8148..fac4d92db 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -56,7 +56,6 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) addChild(&mImage); // Metadata labels + values. - addChild(&mBadges); mLblRating.setText("Rating: "); addChild(&mLblRating); addChild(&mRating); @@ -75,6 +74,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) mLblPlayers.setText("Players: "); addChild(&mLblPlayers); addChild(&mPlayers); + addChild(&mBadges); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); mLastPlayed.setDisplayRelative(true); @@ -111,8 +111,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) initMDValues(); } -void DetailedGameListView::onThemeChanged(const std::shared_ptr& theme) -{ +void DetailedGameListView::onThemeChanged(const std::shared_ptr& theme) { BasicGameListView::onThemeChanged(theme); using namespace ThemeFlags; @@ -122,24 +121,26 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr& them POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE); mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE); + mBadges.applyTheme(theme, getName(), "md_badges", + POSITION | ThemeFlags::SIZE | Z_INDEX | DIRECTION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); initMDLabels(); - std::vector labels = getMDLabels(); + std::vector labels = getMDLabels(); assert(labels.size() == 8); std::vector lblElements = { - "md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher", - "md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"}; + "md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher", + "md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"}; for (unsigned int i = 0; i < labels.size(); i++) labels[i]->applyTheme(theme, getName(), lblElements[i], ALL); initMDValues(); - std::vector values = getMDValues(); - assert(values.size() == 8); - std::vector valElements = {"md_rating", "md_releasedate", "md_developer", - "md_publisher", "md_genre", "md_players", - "md_lastplayed", "md_playcount"}; + std::vector values = getMDValues(); + assert(values.size() == 9); + std::vector valElements = {"md_rating", "md_releasedate", "md_developer", + "md_publisher", "md_genre", "md_players", + "md_badges", "md_lastplayed", "md_playcount"}; for (unsigned int i = 0; i < values.size(); i++) values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); @@ -206,6 +207,8 @@ void DetailedGameListView::initMDValues() mLastPlayed.setFont(defaultFont); mPlayCount.setFont(defaultFont); + mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast(defaultFont->getHeight())); + float bottom = 0.0f; const float colSize = (mSize.x * 0.48f) / 2.0f; @@ -275,6 +278,7 @@ void DetailedGameListView::updateInfoPanel() mGenre.setVisible(false); mLblPlayers.setVisible(false); mPlayers.setVisible(false); + mBadges.setVisible(false); mLblLastPlayed.setVisible(false); mLastPlayed.setVisible(false); mLblPlayCount.setVisible(false); @@ -293,6 +297,7 @@ void DetailedGameListView::updateInfoPanel() mGenre.setVisible(true); mLblPlayers.setVisible(true); mPlayers.setVisible(true); + mBadges.setVisible(true); mLblLastPlayed.setVisible(true); mLastPlayed.setVisible(true); mLblPlayCount.setVisible(true); @@ -379,6 +384,18 @@ void DetailedGameListView::updateInfoPanel() mPublisher.setValue(file->metadata.get("publisher")); mGenre.setValue(file->metadata.get("genre")); mPlayers.setValue(file->metadata.get("players")); + + // Generate badges slots value based on the game metadata. + std::stringstream ss; + ss << (file->metadata.get("favorite").compare("true") ? "" : "favorite "); + ss << (file->metadata.get("completed").compare("true") ? "" : "completed "); + ss << (file->metadata.get("kidgame").compare("true") ? "" : "kidgame "); + ss << (file->metadata.get("broken").compare("true") ? "" : "broken "); + std::string slots = ss.str(); + if (!slots.empty()) + slots.pop_back(); + mBadges.setValue(slots); + mName.setValue(file->metadata.get("name")); if (file->getType() == GAME) { @@ -386,8 +403,7 @@ void DetailedGameListView::updateInfoPanel() mLastPlayed.setValue(file->metadata.get("lastplayed")); mPlayCount.setValue(file->metadata.get("playcount")); } - } - else if (file->getType() == FOLDER) { + } else if (file->getType() == FOLDER) { if (!hideMetaDataFields) { mLastPlayed.setValue(file->metadata.get("lastplayed")); mLblPlayCount.setVisible(false); @@ -449,6 +465,7 @@ std::vector DetailedGameListView::getMDValues() ret.push_back(&mPublisher); ret.push_back(&mGenre); ret.push_back(&mPlayers); + ret.push_back(&mBadges); ret.push_back(&mLastPlayed); ret.push_back(&mPlayCount); return ret; diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index b7b699b25..bb260ae51 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -248,7 +248,6 @@ void VideoGameListView::initMDValues() mGenre.setFont(defaultFont); mPlayers.setFont(defaultFont); - // TODO: Set appropriate default height. mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast(defaultFont->getHeight())); mLastPlayed.setFont(defaultFont); From 97067ae40b13f80313cebbd94cc37ac8b56c5c2e Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 02:17:07 +0200 Subject: [PATCH 012/128] remove flexbox padding when item margins are set --- es-core/src/components/FlexboxComponent.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 8569ea358..b29159188 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -102,7 +102,7 @@ void FlexboxComponent::computeLayout() float anchorYStart = anchorY; // Compute total container size. - glm::vec2 totalSize = {mItemMargin.x, mItemMargin.y}; + glm::vec2 totalSize = {-mItemMargin.x, -mItemMargin.y}; if (mDirection == "row") { totalSize.x += (mItemMargin.x + mItemWidth) * mItemsPerLine; totalSize.y += (mItemMargin.y + maxItemSize.y) * nLines; @@ -121,8 +121,12 @@ void FlexboxComponent::computeLayout() float y = anchorY - anchorOriginY * size.y; // Apply item margin. - x += mItemMargin.x * (directionLine.x >= 0.0f ? 1.0f : -1.0f); - y += mItemMargin.y * (directionLine.y >= 0.0f ? 1.0f : -1.0f); + if ((mDirection == "row" && i % std::max(1, (int) mItemsPerLine) != 0) || + (mDirection == "column" && i >= (int) mItemsPerLine)) + x += mItemMargin.x * (directionLine.x >= 0.0f ? 1.0f : -1.0f); + if ((mDirection == "column" && i % std::max(1, (int) mItemsPerLine) != 0) || + (mDirection == "row" && i >= (int) mItemsPerLine)) + y += mItemMargin.y * (directionLine.y >= 0.0f ? 1.0f : -1.0f); // Apply alignment if (mAlign == ITEM_ALIGN_END) { From 74532d06dbe4fe19697bb6db492b93eac73d466f Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 02:17:41 +0200 Subject: [PATCH 013/128] theme vertical padding reduction --- themes/rbsimple-DE/theme.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index a0fc8861a..43bb3da1b 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -244,7 +244,7 @@ based on: 'recalbox-multi' by the Recalbox community row start 2 - 10 5 + 5 5 .035 From 1d5137d5576e4b85f4bf3879e195fa5d12a9b494 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 02:33:49 +0200 Subject: [PATCH 014/128] fix compiler warning. Remove unnecessary code. --- es-app/src/views/gamelist/DetailedGameListView.cpp | 2 -- es-app/src/views/gamelist/VideoGameListView.cpp | 2 -- es-app/src/views/gamelist/VideoGameListView.h | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index fac4d92db..c6bb5b5aa 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -121,8 +121,6 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr& them POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE); mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE); - mBadges.applyTheme(theme, getName(), "md_badges", - POSITION | ThemeFlags::SIZE | Z_INDEX | DIRECTION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); initMDLabels(); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index bb260ae51..a6a187e0a 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -164,8 +164,6 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) mVideo->applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE); - mBadges.applyTheme(theme, getName(), "md_badges", - POSITION | ThemeFlags::SIZE | Z_INDEX | DIRECTION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); initMDLabels(); diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index 23d181389..3516d8477 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -51,13 +51,13 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; - BadgesComponent mBadges; RatingComponent mRating; DateTimeComponent mReleaseDate; TextComponent mDeveloper; TextComponent mPublisher; TextComponent mGenre; TextComponent mPlayers; + BadgesComponent mBadges; DateTimeComponent mLastPlayed; TextComponent mPlayCount; TextComponent mName; From f962a22e11f38ab006466f21321267270c322498 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 02:43:36 +0200 Subject: [PATCH 015/128] minify badge svg --- resources/graphics/badge_broken.svg | 335 +++++-------------------- resources/graphics/badge_completed.svg | 326 +++++------------------- resources/graphics/badge_favorite.svg | 325 +++++------------------- resources/graphics/badge_kidgame.svg | 321 ++++------------------- 4 files changed, 226 insertions(+), 1081 deletions(-) diff --git a/resources/graphics/badge_broken.svg b/resources/graphics/badge_broken.svg index 874679512..008c23538 100644 --- a/resources/graphics/badge_broken.svg +++ b/resources/graphics/badge_broken.svg @@ -1,292 +1,73 @@ - - - - - - - - - + + + - + image/svg+xml - - + + - - - - - - - - + + + + + - - + + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - + + - - diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg index 0caec97b3..fca3c4ac5 100644 --- a/resources/graphics/badge_completed.svg +++ b/resources/graphics/badge_completed.svg @@ -1,285 +1,71 @@ - - - - - + + + - + image/svg+xml - - + + - - - - - + + + + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + + + - diff --git a/resources/graphics/badge_favorite.svg b/resources/graphics/badge_favorite.svg index 775402871..4d5abaeb8 100644 --- a/resources/graphics/badge_favorite.svg +++ b/resources/graphics/badge_favorite.svg @@ -1,281 +1,70 @@ - - - - - + + + - + image/svg+xml - - + + - - - - - - - - + + + + + - - + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + - diff --git a/resources/graphics/badge_kidgame.svg b/resources/graphics/badge_kidgame.svg index 25b9a92db..13f5a249a 100644 --- a/resources/graphics/badge_kidgame.svg +++ b/resources/graphics/badge_kidgame.svg @@ -1,281 +1,70 @@ - - - - - + + + - + image/svg+xml - - + + - - - - - + + + + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + - From edc761c4f9cfc152c61f94c08fbfb801877e1e8f Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 19:45:05 +0200 Subject: [PATCH 016/128] fix the startup slowdown --- es-core/src/components/BadgesComponent.cpp | 53 ++++++++++++---------- es-core/src/components/BadgesComponent.h | 14 +++--- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index a7a6adc50..9c28733a8 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -13,32 +13,37 @@ #include "ThemeData.h" #include "resources/TextureResource.h" -BadgesComponent::BadgesComponent(Window* window) - : FlexboxComponent(window) -{ - // Define the slots. - mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}; +// Available slot definitions. +const std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}; +std::map BadgesComponent::mBadgeIcons = std::map(); +std::map BadgesComponent::mImageComponents = std::map(); + +BadgesComponent::BadgesComponent(Window *window) + : FlexboxComponent(window) { + if (mBadgeIcons.empty()) { + mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; + mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; + mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.svg"; + mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; + } - mBadgeIcons = std::map(); - mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; - mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; - mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.svg"; - mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; // Create the child ImageComponent for every badge. - mImageComponents = std::map(); - ImageComponent mImageFavorite = ImageComponent(window); - mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, false); - mImageComponents.insert({SLOT_FAVORITE, mImageFavorite}); - ImageComponent mImageCompleted = ImageComponent(window); - mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, false); - mImageComponents.insert({SLOT_COMPLETED, mImageCompleted}); - ImageComponent mImageKids = ImageComponent(window); - mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, false); - mImageComponents.insert({SLOT_KIDS, mImageKids}); - ImageComponent mImageBroken = ImageComponent(window); - mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, false); - mImageComponents.insert({SLOT_BROKEN, mImageBroken}); + if (mImageComponents.empty()) { + ImageComponent mImageFavorite = ImageComponent(window); + mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, true); + mImageComponents.insert({SLOT_FAVORITE, mImageFavorite}); + ImageComponent mImageCompleted = ImageComponent(window); + mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, true); + mImageComponents.insert({SLOT_COMPLETED, mImageCompleted}); + ImageComponent mImageKids = ImageComponent(window); + mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, true); + mImageComponents.insert({SLOT_KIDS, mImageKids}); + ImageComponent mImageBroken = ImageComponent(window); + mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, true); + mImageComponents.insert({SLOT_BROKEN, mImageBroken}); + } + } void BadgesComponent::setValue(const std::string& value) @@ -84,7 +89,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, for (auto& slot : mSlots) { if (properties & PATH && elem->has(slot)) { mBadgeIcons[slot] = elem->get(slot); - mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]); + mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot], false, true); imgChanged = true; } } diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index badaa57c7..79b49fc8a 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -30,19 +30,19 @@ public: std::string getValue() const override; // Should be a list of strings. - void setValue(const std::string& value) override; + void setValue(const std::string &value) override; - virtual void applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, + virtual void applyTheme(const std::shared_ptr &theme, + const std::string &view, + const std::string &element, unsigned int properties) override; virtual std::vector getHelpPrompts() override; private: - std::vector mSlots; - std::map mBadgeIcons; - std::map mImageComponents; + static const std::vector mSlots; + static std::map mBadgeIcons; + static std::map mImageComponents; }; #endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H From 87735cd9158473d7b119777b3c2776b9b8583cd3 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 20:02:03 +0200 Subject: [PATCH 017/128] fix the startup slowdown --- es-core/src/components/BadgesComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 9c28733a8..5b0a569be 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -87,7 +87,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, bool imgChanged = false; for (auto& slot : mSlots) { - if (properties & PATH && elem->has(slot)) { + if (properties & PATH && elem->has(slot) && mBadgeIcons[slot] != elem->get(slot)) { mBadgeIcons[slot] = elem->get(slot); mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot], false, true); imgChanged = true; From 0587b220cc43de572cc22b1651912be28ee02ad1 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 22:26:47 +0200 Subject: [PATCH 018/128] add alternative emulator badge --- es-app/src/views/gamelist/DetailedGameListView.cpp | 1 + es-app/src/views/gamelist/VideoGameListView.cpp | 1 + es-core/src/components/BadgesComponent.cpp | 9 +++++++-- es-core/src/components/BadgesComponent.h | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index c6bb5b5aa..c62eef1b1 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -389,6 +389,7 @@ void DetailedGameListView::updateInfoPanel() ss << (file->metadata.get("completed").compare("true") ? "" : "completed "); ss << (file->metadata.get("kidgame").compare("true") ? "" : "kidgame "); ss << (file->metadata.get("broken").compare("true") ? "" : "broken "); + ss << (file->metadata.get("altemulator").compare("") ? "altemu " : ""); std::string slots = ss.str(); if (!slots.empty()) slots.pop_back(); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index a6a187e0a..49b25ddd1 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -451,6 +451,7 @@ void VideoGameListView::updateInfoPanel() ss << (file->metadata.get("completed").compare("true") ? "" : "completed "); ss << (file->metadata.get("kidgame").compare("true") ? "" : "kidgame "); ss << (file->metadata.get("broken").compare("true") ? "" : "broken "); + ss << (file->metadata.get("altemulator").compare("") ? "altemu " : ""); std::string slots = ss.str(); if (!slots.empty()) slots.pop_back(); diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 5b0a569be..48bcc1f78 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -14,7 +14,8 @@ #include "resources/TextureResource.h" // Available slot definitions. -const std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}; +const std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN, + SLOT_ALTERNATIVE_EMULATOR}; std::map BadgesComponent::mBadgeIcons = std::map(); std::map BadgesComponent::mImageComponents = std::map(); @@ -25,6 +26,7 @@ BadgesComponent::BadgesComponent(Window *window) mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.svg"; mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; + mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR] = ":/graphics/badge_altemu.svg"; } @@ -42,6 +44,9 @@ BadgesComponent::BadgesComponent(Window *window) ImageComponent mImageBroken = ImageComponent(window); mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, true); mImageComponents.insert({SLOT_BROKEN, mImageBroken}); + ImageComponent mImageAltEmu = ImageComponent(window); + mImageAltEmu.setImage(mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR], false, true); + mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); } } @@ -54,7 +59,7 @@ void BadgesComponent::setValue(const std::string& value) std::istringstream ss(value); while (std::getline(ss, temp, ' ')) { if (!(temp == SLOT_FAVORITE || temp == SLOT_COMPLETED || temp == SLOT_KIDS || - temp == SLOT_BROKEN)) + temp == SLOT_BROKEN || temp == SLOT_ALTERNATIVE_EMULATOR)) LOG(LogError) << "Badge slot '" << temp << "' is invalid."; else mChildren.push_back(&mImageComponents.find(temp)->second); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 79b49fc8a..6c3c1509e 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -20,6 +20,7 @@ #define SLOT_COMPLETED "completed" #define SLOT_KIDS "kidgame" #define SLOT_BROKEN "broken" +#define SLOT_ALTERNATIVE_EMULATOR "altemu" class TextureResource; From a4d4493d3ea6194bd123d2fe58122b6ab2928f6c Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 22:45:10 +0200 Subject: [PATCH 019/128] fix segfault on application exit --- es-core/src/components/BadgesComponent.cpp | 10 +++++++--- es-core/src/components/BadgesComponent.h | 4 +++- es-core/src/components/ImageComponent.h | 14 ++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 48bcc1f78..8adff44a1 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -48,11 +48,15 @@ BadgesComponent::BadgesComponent(Window *window) mImageAltEmu.setImage(mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR], false, true); mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); } - } -void BadgesComponent::setValue(const std::string& value) -{ +BadgesComponent::~BadgesComponent() { + mBadgeIcons.clear(); + mImageComponents.clear(); +} + + +void BadgesComponent::setValue(const std::string &value) { mChildren.clear(); if (!value.empty()) { std::string temp; diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 6c3c1509e..7da63e447 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -27,7 +27,9 @@ class TextureResource; class BadgesComponent : public FlexboxComponent { public: - BadgesComponent(Window* window); + BadgesComponent(Window *window); + + ~BadgesComponent() noexcept; std::string getValue() const override; // Should be a list of strings. diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index ca537c62f..2d7b0c49d 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -14,21 +14,23 @@ class TextureResource; -class ImageComponent : public GuiComponent -{ +class ImageComponent : public GuiComponent { public: - ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true); - virtual ~ImageComponent() {} + ImageComponent(Window *window, bool forceLoad = false, bool dynamic = true); + + virtual ~ImageComponent() noexcept {}; void setDefaultImage(std::string path) { mDefaultPath = path; } // Loads the image at the given filepath. Will tile if tile is true (retrieves texture // as tiling, creates vertices accordingly). void setImage(std::string path, bool tile = false, bool linearMagnify = false); + // Loads an image from memory. - void setImage(const char* data, size_t length, bool tile = false); + void setImage(const char *data, size_t length, bool tile = false); + // Use an already existing texture. - void setImage(const std::shared_ptr& texture); + void setImage(const std::shared_ptr &texture); void onSizeChanged() override { updateVertices(); } From e2c1d2d0ef50c0da24b60fc94529891055cf8043 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 22:59:14 +0200 Subject: [PATCH 020/128] fix segfault in badge destructor fix margins --- es-core/src/components/BadgesComponent.cpp | 3 +-- es-core/src/components/FlexboxComponent.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 8adff44a1..e2fd9bfe4 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -51,8 +51,7 @@ BadgesComponent::BadgesComponent(Window *window) } BadgesComponent::~BadgesComponent() { - mBadgeIcons.clear(); - mImageComponents.clear(); + mChildren.clear(); } diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index b29159188..6665b2883 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -121,12 +121,12 @@ void FlexboxComponent::computeLayout() float y = anchorY - anchorOriginY * size.y; // Apply item margin. - if ((mDirection == "row" && i % std::max(1, (int) mItemsPerLine) != 0) || + /*if ((mDirection == "row" && i % std::max(1, (int) mItemsPerLine) != 0) || (mDirection == "column" && i >= (int) mItemsPerLine)) x += mItemMargin.x * (directionLine.x >= 0.0f ? 1.0f : -1.0f); if ((mDirection == "column" && i % std::max(1, (int) mItemsPerLine) != 0) || (mDirection == "row" && i >= (int) mItemsPerLine)) - y += mItemMargin.y * (directionLine.y >= 0.0f ? 1.0f : -1.0f); + y += mItemMargin.y * (directionLine.y >= 0.0f ? 1.0f : -1.0f);*/ // Apply alignment if (mAlign == ITEM_ALIGN_END) { From 2b8c95d2efa35337cc11669f2ef0ff4be0c6f999 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 23:04:09 +0200 Subject: [PATCH 021/128] fix segfault in badge destructor --- es-core/src/GuiComponent.h | 5 +++-- es-core/src/components/BadgesComponent.cpp | 2 ++ es-core/src/resources/TextureResource.h | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 386b78ef0..2e7efd9a5 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -37,8 +37,9 @@ class Window; class GuiComponent { public: - GuiComponent(Window* window); - virtual ~GuiComponent(); + GuiComponent(Window *window); + + virtual ~GuiComponent() noexcept; virtual void textInput(const std::string& text); diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index e2fd9bfe4..1a5b5f341 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -52,6 +52,8 @@ BadgesComponent::BadgesComponent(Window *window) BadgesComponent::~BadgesComponent() { mChildren.clear(); + mBadgeIcons.clear(); + mImageComponents.clear(); } diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 223d60fcc..bed111536 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -47,7 +47,7 @@ public: void rasterizeAt(size_t width, size_t height); glm::vec2 getSourceImageSize() const { return mSourceSize; } - virtual ~TextureResource(); + virtual ~TextureResource() noexcept; bool isInitialized() const { return true; } bool isTiled() const; From 39f67cfcdfedaaf3f341dcfe553bf66c2bb834f1 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sun, 26 Sep 2021 23:58:23 +0200 Subject: [PATCH 022/128] finally properly implemented destructor --- es-app/src/views/gamelist/VideoGameListView.h | 5 +++-- es-core/src/components/BadgesComponent.cpp | 16 ++++++++++++---- es-core/src/components/BadgesComponent.h | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index 3516d8477..170a0dbbd 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -20,8 +20,9 @@ class VideoComponent; class VideoGameListView : public BasicGameListView { public: - VideoGameListView(Window* window, FileData* root); - virtual ~VideoGameListView(); + VideoGameListView(Window *window, FileData *root); + + virtual ~VideoGameListView() noexcept; virtual void onShow() override; virtual void onThemeChanged(const std::shared_ptr& theme) override; diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 1a5b5f341..b0f948215 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -18,6 +18,7 @@ const std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_CO SLOT_ALTERNATIVE_EMULATOR}; std::map BadgesComponent::mBadgeIcons = std::map(); std::map BadgesComponent::mImageComponents = std::map(); +std::vector BadgesComponent::mInstances = {}; BadgesComponent::BadgesComponent(Window *window) : FlexboxComponent(window) { @@ -48,12 +49,19 @@ BadgesComponent::BadgesComponent(Window *window) mImageAltEmu.setImage(mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR], false, true); mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); } + + mInstances.push_back(this); } -BadgesComponent::~BadgesComponent() { - mChildren.clear(); - mBadgeIcons.clear(); - mImageComponents.clear(); +BadgesComponent::~BadgesComponent() noexcept { + for (GuiComponent *c: mChildren) + c->clearChildren(); + clearChildren(); + mInstances.erase(std::remove(mInstances.begin(), mInstances.end(), this), mInstances.end()); + if (mInstances.empty()) { + mBadgeIcons.clear(); + mImageComponents.clear(); + } } diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 7da63e447..4a54fa323 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -28,7 +28,6 @@ class BadgesComponent : public FlexboxComponent { public: BadgesComponent(Window *window); - ~BadgesComponent() noexcept; std::string getValue() const override; @@ -46,6 +45,7 @@ private: static const std::vector mSlots; static std::map mBadgeIcons; static std::map mImageComponents; + static std::vector mInstances; }; #endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H From 593b6d94ee7dc90988fb25355639290df8709b2f Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Mon, 27 Sep 2021 00:41:53 +0200 Subject: [PATCH 023/128] remove obtrusive static image component and add svg caching --- es-core/src/components/BadgesComponent.cpp | 61 +++++++++------------- es-core/src/components/BadgesComponent.h | 5 +- es-core/src/components/ImageComponent.cpp | 7 ++- es-core/src/components/ImageComponent.h | 2 +- es-core/src/resources/TextureResource.cpp | 7 +-- es-core/src/resources/TextureResource.h | 8 +-- 6 files changed, 40 insertions(+), 50 deletions(-) diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index b0f948215..bfa210770 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -16,52 +16,41 @@ // Available slot definitions. const std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN, SLOT_ALTERNATIVE_EMULATOR}; -std::map BadgesComponent::mBadgeIcons = std::map(); -std::map BadgesComponent::mImageComponents = std::map(); -std::vector BadgesComponent::mInstances = {}; BadgesComponent::BadgesComponent(Window *window) : FlexboxComponent(window) { - if (mBadgeIcons.empty()) { - mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; - mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; - mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.svg"; - mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; - mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR] = ":/graphics/badge_altemu.svg"; - } + mBadgeIcons = std::map(); + mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; + mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; + mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.svg"; + mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; + mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR] = ":/graphics/badge_altemu.svg"; - // Create the child ImageComponent for every badge. - if (mImageComponents.empty()) { - ImageComponent mImageFavorite = ImageComponent(window); - mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, true); - mImageComponents.insert({SLOT_FAVORITE, mImageFavorite}); - ImageComponent mImageCompleted = ImageComponent(window); - mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, true); - mImageComponents.insert({SLOT_COMPLETED, mImageCompleted}); - ImageComponent mImageKids = ImageComponent(window); - mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, true); - mImageComponents.insert({SLOT_KIDS, mImageKids}); - ImageComponent mImageBroken = ImageComponent(window); - mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, true); - mImageComponents.insert({SLOT_BROKEN, mImageBroken}); - ImageComponent mImageAltEmu = ImageComponent(window); - mImageAltEmu.setImage(mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR], false, true); - mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); - } - - mInstances.push_back(this); + mImageComponents = std::map(); + ImageComponent mImageFavorite = ImageComponent(window); + mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, true, true); + mImageComponents.insert({SLOT_FAVORITE, mImageFavorite}); + ImageComponent mImageCompleted = ImageComponent(window); + mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, true, true); + mImageComponents.insert({SLOT_COMPLETED, mImageCompleted}); + ImageComponent mImageKids = ImageComponent(window); + mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, true, true); + mImageComponents.insert({SLOT_KIDS, mImageKids}); + ImageComponent mImageBroken = ImageComponent(window); + mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, true, true); + mImageComponents.insert({SLOT_BROKEN, mImageBroken}); + ImageComponent mImageAltEmu = ImageComponent(window); + mImageAltEmu.setImage(mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR], false, true, true); + mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); } BadgesComponent::~BadgesComponent() noexcept { for (GuiComponent *c: mChildren) c->clearChildren(); clearChildren(); - mInstances.erase(std::remove(mInstances.begin(), mInstances.end(), this), mInstances.end()); - if (mInstances.empty()) { - mBadgeIcons.clear(); - mImageComponents.clear(); - } + mBadgeIcons.clear(); + mImageComponents.clear(); } @@ -107,7 +96,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, for (auto& slot : mSlots) { if (properties & PATH && elem->has(slot) && mBadgeIcons[slot] != elem->get(slot)) { mBadgeIcons[slot] = elem->get(slot); - mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot], false, true); + mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot], false, true, true); imgChanged = true; } } diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 4a54fa323..2dbc9dc1d 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -43,9 +43,8 @@ public: private: static const std::vector mSlots; - static std::map mBadgeIcons; - static std::map mImageComponents; - static std::vector mInstances; + std::map mBadgeIcons; + std::map mImageComponents; }; #endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 5891074d1..0787d53d5 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -126,8 +126,7 @@ void ImageComponent::resize() onSizeChanged(); } -void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify) -{ +void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify, bool cacheImage) { // Always load bundled graphic resources statically, unless mForceLoad has been set. // This eliminates annoying texture pop-in problems that would otherwise occur. if (!mForceLoad && (path[0] == ':') && (path[1] == '/')) { @@ -139,10 +138,10 @@ void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify) mTexture.reset(); else mTexture = - TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, linearMagnify); + TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, linearMagnify, 1.0f, cacheImage); } else { - mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, linearMagnify); + mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, linearMagnify, 1.0f, cacheImage); } resize(); diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 2d7b0c49d..c0bb05c91 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -24,7 +24,7 @@ public: // Loads the image at the given filepath. Will tile if tile is true (retrieves texture // as tiling, creates vertices accordingly). - void setImage(std::string path, bool tile = false, bool linearMagnify = false); + void setImage(std::string path, bool tile = false, bool linearMagnify = false, bool cacheSVG = false); // Loads an image from memory. void setImage(const char *data, size_t length, bool tile = false); diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index 33fec7e8a..317a8c99d 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -143,12 +143,13 @@ bool TextureResource::bind() } } -std::shared_ptr TextureResource::get(const std::string& path, +std::shared_ptr TextureResource::get(const std::string &path, bool tile, bool forceLoad, bool dynamic, bool linearMagnify, - float scaleDuringLoad) + float scaleDuringLoad, + bool cacheImage) { std::shared_ptr& rm = ResourceManager::getInstance(); @@ -176,7 +177,7 @@ std::shared_ptr TextureResource::get(const std::string& path, std::shared_ptr data = sTextureDataManager.get(tex.get()); // Is it an SVG? - if (key.first.substr(key.first.size() - 4, std::string::npos) != ".svg") { + if (key.first.substr(key.first.size() - 4, std::string::npos) != ".svg" || cacheImage) { // Probably not. Add it to our map. We don't add SVGs because 2 SVGs might be // rasterized at different sizes. sTextureMap[key] = std::weak_ptr(tex); diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index bed111536..0f6c42f6f 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -25,13 +25,15 @@ class TextureData; class TextureResource : public IReloadable { public: - static std::shared_ptr get(const std::string& path, + static std::shared_ptr get(const std::string &path, bool tile = false, bool forceLoad = false, bool dynamic = true, bool linearMagnify = false, - float scaleDuringLoad = 1.0f); - void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height); + float scaleDuringLoad = 1.0f, + bool cacheImage = false); + + void initFromPixels(const unsigned char *dataRGBA, size_t width, size_t height); virtual void initFromMemory(const char* data, size_t length); static void manualUnload(std::string path, bool tile); From a928142d5b6181d4e408af19815f6548f9bd8f32 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Mon, 27 Sep 2021 01:50:45 +0200 Subject: [PATCH 024/128] make slots configurable by theme (fix) --- es-core/src/components/BadgesComponent.cpp | 23 +++++++++++++++++----- es-core/src/components/BadgesComponent.h | 2 +- themes/rbsimple-DE/theme.xml | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index bfa210770..5fb20c41c 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -14,8 +14,8 @@ #include "resources/TextureResource.h" // Available slot definitions. -const std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN, - SLOT_ALTERNATIVE_EMULATOR}; +std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN, + SLOT_ALTERNATIVE_EMULATOR}; BadgesComponent::BadgesComponent(Window *window) : FlexboxComponent(window) { @@ -63,7 +63,7 @@ void BadgesComponent::setValue(const std::string &value) { if (!(temp == SLOT_FAVORITE || temp == SLOT_COMPLETED || temp == SLOT_KIDS || temp == SLOT_BROKEN || temp == SLOT_ALTERNATIVE_EMULATOR)) LOG(LogError) << "Badge slot '" << temp << "' is invalid."; - else + else if (std::find(mSlots.begin(), mSlots.end(), temp) != mSlots.end()) mChildren.push_back(&mImageComponents.find(temp)->second); } } @@ -101,8 +101,21 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, } } - if (elem->has("slots")) - setValue(elem->get("slots")); + if (elem->has("slots")) { + auto value = elem->get("slots"); + mSlots = {}; + if (!value.empty()) { + std::string temp; + std::istringstream ss(value); + while (std::getline(ss, temp, ' ')) { + if (!(temp == SLOT_FAVORITE || temp == SLOT_COMPLETED || temp == SLOT_KIDS || + temp == SLOT_BROKEN || temp == SLOT_ALTERNATIVE_EMULATOR)) + LOG(LogError) << "Badge slot '" << temp << "' is invalid."; + else + mSlots.push_back(temp); + } + } + } // Apply theme on the flexbox component parent. FlexboxComponent::applyTheme(theme, view, element, properties); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 2dbc9dc1d..ce158012b 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -42,7 +42,7 @@ public: virtual std::vector getHelpPrompts() override; private: - static const std::vector mSlots; + static std::vector mSlots; std::map mBadgeIcons; std::map mImageComponents; }; diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 43bb3da1b..381379f96 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -248,7 +248,7 @@ based on: 'recalbox-multi' by the Recalbox community .035 - favorite completed kidgame broken + favorite completed kidgame broken altemu :/graphics/badge_favorite.svg :/graphics/badge_completed.svg :/graphics/badge_kidgame.svg From f6dd49071e8b9a3fe2593f62bdba2b81c6f8c43b Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 27 Sep 2021 21:06:07 +0200 Subject: [PATCH 025/128] Fixed lots of code formatting issues. --- es-app/src/CollectionSystemsManager.cpp | 41 +-- es-app/src/FileData.cpp | 26 +- es-app/src/FileFilterIndex.cpp | 24 +- es-app/src/FileFilterIndex.h | 12 +- es-app/src/FileSorts.cpp | 3 +- es-app/src/FileSorts.h | 36 +-- .../src/guis/GuiCollectionSystemsOptions.cpp | 17 +- es-app/src/guis/GuiGameScraper.cpp | 11 +- es-app/src/guis/GuiGamelistFilter.cpp | 15 +- es-app/src/guis/GuiGamelistOptions.cpp | 12 +- es-app/src/guis/GuiLaunchScreen.cpp | 12 +- es-app/src/guis/GuiLaunchScreen.h | 10 +- es-app/src/guis/GuiMenu.cpp | 25 +- es-app/src/guis/GuiMetaDataEd.cpp | 47 +-- es-app/src/guis/GuiOfflineGenerator.cpp | 8 +- es-app/src/guis/GuiScraperMulti.cpp | 12 +- es-app/src/guis/GuiScraperSearch.cpp | 43 ++- es-app/src/guis/GuiScraperSearch.h | 15 +- es-app/src/guis/GuiSettings.cpp | 31 +- es-app/src/scrapers/GamesDBJSONScraper.cpp | 13 +- .../views/gamelist/DetailedGameListView.cpp | 52 +++- .../src/views/gamelist/GridGameListView.cpp | 31 +- es-app/src/views/gamelist/GridGameListView.h | 4 +- es-app/src/views/gamelist/VideoGameListView.h | 2 +- es-core/src/GuiComponent.cpp | 21 +- es-core/src/GuiComponent.h | 10 +- es-core/src/HttpReq.h | 2 +- es-core/src/components/BadgesComponent.cpp | 21 +- es-core/src/components/BadgesComponent.h | 10 +- es-core/src/components/ButtonComponent.cpp | 64 ++-- es-core/src/components/ButtonComponent.h | 21 +- es-core/src/components/ComponentGrid.cpp | 24 +- es-core/src/components/ComponentGrid.h | 41 +-- es-core/src/components/ComponentList.cpp | 7 +- .../src/components/DateTimeEditComponent.cpp | 15 +- es-core/src/components/FlexboxComponent.cpp | 27 +- es-core/src/components/HelpComponent.cpp | 34 +- es-core/src/components/ImageComponent.cpp | 32 +- es-core/src/components/ImageComponent.h | 14 +- es-core/src/guis/GuiInputConfig.cpp | 2 +- es-core/src/guis/GuiMsgBox.cpp | 24 +- es-core/src/guis/GuiTextEditKeyboardPopup.cpp | 292 ++++++++++-------- es-core/src/guis/GuiTextEditKeyboardPopup.h | 56 ++-- es-core/src/guis/GuiTextEditPopup.cpp | 116 ++++--- es-core/src/guis/GuiTextEditPopup.h | 31 +- es-core/src/resources/Font.cpp | 12 +- es-core/src/resources/TextureResource.cpp | 2 +- es-core/src/resources/TextureResource.h | 4 +- 48 files changed, 828 insertions(+), 556 deletions(-) diff --git a/es-app/src/CollectionSystemsManager.cpp b/es-app/src/CollectionSystemsManager.cpp index f4b7823d4..282b880a2 100644 --- a/es-app/src/CollectionSystemsManager.cpp +++ b/es-app/src/CollectionSystemsManager.cpp @@ -377,21 +377,21 @@ void CollectionSystemsManager::updateCollectionSystem(FileData* file, Collection // If the countasgame flag has been set to false, then remove the game. if (curSys->isGroupedCustomCollection()) { ViewController::get() - ->getGameListView(curSys->getRootFolder()->getParent()->getSystem()) - .get() - ->remove(collectionEntry, false); - FileData *parentRootFolder = - rootFolder->getParent()->getSystem()->getRootFolder(); + ->getGameListView(curSys->getRootFolder()->getParent()->getSystem()) + .get() + ->remove(collectionEntry, false); + FileData* parentRootFolder = + rootFolder->getParent()->getSystem()->getRootFolder(); parentRootFolder->sort(parentRootFolder->getSortTypeFromString( - parentRootFolder->getSortTypeString()), + parentRootFolder->getSortTypeString()), mFavoritesSorting); - GuiInfoPopup *s = new GuiInfoPopup( - mWindow, - "DISABLED '" + + GuiInfoPopup* s = new GuiInfoPopup( + mWindow, + "DISABLED '" + Utils::String::toUpper( - Utils::String::removeParenthesis(file->getName())) + + Utils::String::removeParenthesis(file->getName())) + "' IN '" + Utils::String::toUpper(sysData.system->getName()) + "'", - 4000); + 4000); mWindow->setInfoPopup(s); } else { @@ -550,26 +550,28 @@ bool CollectionSystemsManager::isThemeCustomCollectionCompatible( return true; } -std::string CollectionSystemsManager::getValidNewCollectionName(std::string inName, int index) { +std::string CollectionSystemsManager::getValidNewCollectionName(std::string inName, int index) +{ std::string name = inName; // Trim leading and trailing whitespaces. name.erase(name.begin(), std::find_if(name.begin(), name.end(), [](char c) { - return !std::isspace(static_cast(c)); - })); + return !std::isspace(static_cast(c)); + })); name.erase(std::find_if(name.rbegin(), name.rend(), [](char c) { return !std::isspace(static_cast(c)); }) - .base(), + .base(), name.end()); if (index == 0) { size_t remove = std::string::npos; // Get valid name. while ((remove = name.find_first_not_of( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-[]()' ")) != + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-[]()' ")) != std::string::npos) name.erase(remove, 1); - } else { + } + else { name += " (" + std::to_string(index) + ")"; } @@ -1331,7 +1333,8 @@ void CollectionSystemsManager::addEnabledCollectionsToDisplayedSystems( } } -std::vector CollectionSystemsManager::getSystemsFromConfig() { +std::vector CollectionSystemsManager::getSystemsFromConfig() +{ std::vector systems; std::vector configPaths = SystemData::getConfigPath(false); @@ -1339,7 +1342,7 @@ std::vector CollectionSystemsManager::getSystemsFromConfig() { // file under ~/.emulationstation/custom_systems as we really want to include all the themes // supported by ES-DE. Otherwise a user may accidentally create a custom collection that // corresponds to a supported theme. - for (auto path: configPaths) { + for (auto path : configPaths) { if (!Utils::FileSystem::exists(path)) return systems; diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index c3fdf9103..8b441da80 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -30,11 +30,18 @@ #include FileData::FileData(FileType type, - const std::string &path, - SystemEnvironmentData *envData, - SystemData *system) - : metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA), mSourceFileData(nullptr), mParent(nullptr), - mType(type), mPath(path), mEnvData(envData), mSystem(system), mOnlyFolders(false), mDeletionFlag(false) + const std::string& path, + SystemEnvironmentData* envData, + SystemData* system) + : metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) + , mSourceFileData(nullptr) + , mParent(nullptr) + , mType(type) + , mPath(path) + , mEnvData(envData) + , mSystem(system) + , mOnlyFolders(false) + , mDeletionFlag(false) { // Metadata needs at least a name field (since that's what getName() will return). if (metadata.get("name").empty()) { @@ -735,10 +742,11 @@ FileData::SortType FileData::getSortTypeFromString(std::string desc) return FileSorts::SortTypes.at(0); } -void FileData::launchGame(Window *window) { +void FileData::launchGame(Window* window) +{ LOG(LogInfo) << "Launching game \"" << this->metadata.get("name") << "\"..."; - SystemData *gameSystem = nullptr; + SystemData* gameSystem = nullptr; std::string command = ""; std::string alternativeEmulator; @@ -1347,8 +1355,8 @@ const std::string& CollectionFileData::getName() if (mDirty) { mCollectionFileName = mSourceFileData->metadata.get("name"); mCollectionFileName.append(" [") - .append(Utils::String::toUpper(mSourceFileData->getSystem()->getName())) - .append("]"); + .append(Utils::String::toUpper(mSourceFileData->getSystem()->getName())) + .append("]"); mDirty = false; } diff --git a/es-app/src/FileFilterIndex.cpp b/es-app/src/FileFilterIndex.cpp index 4e464b020..b49be5f5d 100644 --- a/es-app/src/FileFilterIndex.cpp +++ b/es-app/src/FileFilterIndex.cpp @@ -20,9 +20,17 @@ #define INCLUDE_UNKNOWN false; FileFilterIndex::FileFilterIndex() - : mFilterByText(false), mTextRemoveSystem(false), mFilterByFavorites(false), mFilterByGenre(false), - mFilterByPlayers(false), mFilterByPubDev(false), mFilterByRatings(false), mFilterByKidGame(false), - mFilterByCompleted(false), mFilterByBroken(false), mFilterByHidden(false) + : mFilterByText(false) + , mTextRemoveSystem(false) + , mFilterByFavorites(false) + , mFilterByGenre(false) + , mFilterByPlayers(false) + , mFilterByPubDev(false) + , mFilterByRatings(false) + , mFilterByKidGame(false) + , mFilterByCompleted(false) + , mFilterByBroken(false) + , mFilterByHidden(false) { clearAllFilters(); @@ -357,10 +365,11 @@ bool FileFilterIndex::showFile(FileData* game) // in [] from the search string. if (mTextFilter != "" && mTextRemoveSystem && !(Utils::String::toUpper(game->getName().substr(0, game->getName().find_last_of("["))) - .find(mTextFilter) != std::string::npos)) { + .find(mTextFilter) != std::string::npos)) { return false; - } else if (mTextFilter != "" && - !(Utils::String::toUpper(game->getName()).find(mTextFilter) != std::string::npos)) { + } + else if (mTextFilter != "" && + !(Utils::String::toUpper(game->getName()).find(mTextFilter) != std::string::npos)) { return false; } @@ -372,7 +381,8 @@ bool FileFilterIndex::showFile(FileData* game) FilterDataDecl filterData = (*it); if (filterData.primaryKey == "kidgame" && UIModeController::getInstance()->isUIModeKid()) { return (getIndexableKey(game, filterData.type, false) != "FALSE"); - } else if (*(filterData.filteredByRef)) { + } + else if (*(filterData.filteredByRef)) { // Try to find a match. std::string key = getIndexableKey(game, filterData.type, false); keepGoing = isKeyBeingFilteredBy(key, filterData.type); diff --git a/es-app/src/FileFilterIndex.h b/es-app/src/FileFilterIndex.h index 3e2af831e..ac0c3f0e4 100644 --- a/es-app/src/FileFilterIndex.h +++ b/es-app/src/FileFilterIndex.h @@ -49,11 +49,11 @@ public: FileFilterIndex(); ~FileFilterIndex(); - void addToIndex(FileData *game); + void addToIndex(FileData* game); - void removeFromIndex(FileData *game); + void removeFromIndex(FileData* game); - void setFilter(FilterIndexType type, std::vector *values); + void setFilter(FilterIndexType type, std::vector* values); void setTextFilter(std::string textFilter); @@ -63,17 +63,17 @@ public: void debugPrintIndexes(); - bool showFile(FileData *game); + bool showFile(FileData* game); bool isFiltered(); bool isKeyBeingFilteredBy(std::string key, FilterIndexType type); - std::vector &getFilterDataDecls() { return filterDataDecl; } + std::vector& getFilterDataDecls() { return filterDataDecl; } void setTextRemoveSystem(bool status) { mTextRemoveSystem = status; } - void importIndex(FileFilterIndex *indexToImport); + void importIndex(FileFilterIndex* indexToImport); void resetIndex(); diff --git a/es-app/src/FileSorts.cpp b/es-app/src/FileSorts.cpp index 196244391..916ecd819 100644 --- a/es-app/src/FileSorts.cpp +++ b/es-app/src/FileSorts.cpp @@ -227,7 +227,8 @@ namespace FileSorts return system1.compare(system2) < 0; } - bool compareSystemDescending(const FileData *file1, const FileData *file2) { + bool compareSystemDescending(const FileData* file1, const FileData* file2) + { std::string system1 = Utils::String::toUpper(file1->getSystemName()); std::string system2 = Utils::String::toUpper(file2->getSystemName()); return system1.compare(system2) > 0; diff --git a/es-app/src/FileSorts.h b/es-app/src/FileSorts.h index 950878c27..424acf109 100644 --- a/es-app/src/FileSorts.h +++ b/es-app/src/FileSorts.h @@ -19,41 +19,41 @@ namespace FileSorts bool compareName(const FileData* file1, const FileData* file2); bool compareNameDescending(const FileData* file1, const FileData* file2); - bool compareRating(const FileData *file1, const FileData *file2); + bool compareRating(const FileData* file1, const FileData* file2); - bool compareRatingDescending(const FileData *file1, const FileData *file2); + bool compareRatingDescending(const FileData* file1, const FileData* file2); - bool compareReleaseDate(const FileData *file1, const FileData *file2); + bool compareReleaseDate(const FileData* file1, const FileData* file2); - bool compareReleaseDateDescending(const FileData *file1, const FileData *file2); + bool compareReleaseDateDescending(const FileData* file1, const FileData* file2); - bool compareDeveloper(const FileData *file1, const FileData *file2); + bool compareDeveloper(const FileData* file1, const FileData* file2); - bool compareDeveloperDescending(const FileData *file1, const FileData *file2); + bool compareDeveloperDescending(const FileData* file1, const FileData* file2); - bool comparePublisher(const FileData *file1, const FileData *file2); + bool comparePublisher(const FileData* file1, const FileData* file2); - bool comparePublisherDescending(const FileData *file1, const FileData *file2); + bool comparePublisherDescending(const FileData* file1, const FileData* file2); - bool compareGenre(const FileData *file1, const FileData *file2); + bool compareGenre(const FileData* file1, const FileData* file2); - bool compareGenreDescending(const FileData *file1, const FileData *file2); + bool compareGenreDescending(const FileData* file1, const FileData* file2); - bool compareNumPlayers(const FileData *file1, const FileData *file2); + bool compareNumPlayers(const FileData* file1, const FileData* file2); - bool compareNumPlayersDescending(const FileData *file1, const FileData *file2); + bool compareNumPlayersDescending(const FileData* file1, const FileData* file2); - bool compareLastPlayed(const FileData *file1, const FileData *file2); + bool compareLastPlayed(const FileData* file1, const FileData* file2); - bool compareLastPlayedDescending(const FileData *file1, const FileData *file2); + bool compareLastPlayedDescending(const FileData* file1, const FileData* file2); - bool compareTimesPlayed(const FileData *file1, const FileData *fil2); + bool compareTimesPlayed(const FileData* file1, const FileData* fil2); - bool compareTimesPlayedDescending(const FileData *file1, const FileData *fil2); + bool compareTimesPlayedDescending(const FileData* file1, const FileData* fil2); - bool compareSystem(const FileData *file1, const FileData *file2); + bool compareSystem(const FileData* file1, const FileData* file2); - bool compareSystemDescending(const FileData *file1, const FileData *file2); + bool compareSystemDescending(const FileData* file1, const FileData* file2); extern const std::vector SortTypes; } // namespace FileSorts diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp index cf4298c2d..59cc0052e 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -200,12 +200,12 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st glm::vec2{0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); row.addElement(newCollection, true); row.addElement(bracketNewCollection, false); - auto createCollectionCall = [this](const std::string &newVal) { + auto createCollectionCall = [this](const std::string& newVal) { std::string name = newVal; // We need to store the first GUI and remove it, as it'll be deleted // by the actual GUI. - Window *window = mWindow; - GuiComponent *topGui = window->peekGui(); + Window* window = mWindow; + GuiComponent* topGui = window->peekGui(); window->removeGui(topGui); createCustomCollection(name); }; @@ -213,10 +213,11 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st if (Settings::getInstance()->getBool("VirtualKeyboard")) { row.makeAcceptInputHandler([this, createCollectionCall] { mWindow->pushGui(new GuiTextEditKeyboardPopup( - mWindow, getHelpStyle(), "New Collection Name", "", createCollectionCall, false, - "CREATE", "CREATE COLLECTION?")); + mWindow, getHelpStyle(), "New Collection Name", "", createCollectionCall, false, + "CREATE", "CREATE COLLECTION?")); }); - } else { + } + else { row.makeAcceptInputHandler([this, createCollectionCall] { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "New Collection Name", "", createCollectionCall, false, "CREATE", @@ -228,11 +229,11 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st // Delete custom collection. row.elements.clear(); auto deleteCollection = std::make_shared( - mWindow, "DELETE CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + mWindow, "DELETE CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); auto bracketDeleteCollection = std::make_shared(mWindow); bracketDeleteCollection->setImage(":/graphics/arrow.svg"); bracketDeleteCollection->setResize( - glm::vec2{0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); + glm::vec2{0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); row.addElement(deleteCollection, true); row.addElement(bracketDeleteCollection, false); row.makeAcceptInputHandler([this, customSystems] { diff --git a/es-app/src/guis/GuiGameScraper.cpp b/es-app/src/guis/GuiGameScraper.cpp index 3f65554fa..9ddc85caa 100644 --- a/es-app/src/guis/GuiGameScraper.cpp +++ b/es-app/src/guis/GuiGameScraper.cpp @@ -18,11 +18,14 @@ #include "components/TextComponent.h" #include "views/ViewController.h" -GuiGameScraper::GuiGameScraper(Window *window, +GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, - std::function doneFunc) - : GuiComponent(window), mClose(false), mGrid(window, glm::ivec2{1, 7}), mBox(window, ":/graphics/frame.svg"), - mSearchParams(params) + std::function doneFunc) + : GuiComponent(window) + , mClose(false) + , mGrid(window, glm::ivec2{1, 7}) + , mBox(window, ":/graphics/frame.svg") + , mSearchParams(params) { addChild(&mBox); addChild(&mGrid); diff --git a/es-app/src/guis/GuiGamelistFilter.cpp b/es-app/src/guis/GuiGamelistFilter.cpp index 8330b4424..679e785cb 100644 --- a/es-app/src/guis/GuiGamelistFilter.cpp +++ b/es-app/src/guis/GuiGamelistFilter.cpp @@ -29,7 +29,8 @@ GuiGamelistFilter::GuiGamelistFilter(Window* window, initializeMenu(); } -void GuiGamelistFilter::initializeMenu() { +void GuiGamelistFilter::initializeMenu() +{ addChild(&mMenu); // Get filters from system. @@ -93,12 +94,13 @@ void GuiGamelistFilter::resetAllFilters() GuiGamelistFilter::~GuiGamelistFilter() { mFilterOptions.clear(); } -void GuiGamelistFilter::addFiltersToMenu() { +void GuiGamelistFilter::addFiltersToMenu() +{ ComponentListRow row; auto lbl = std::make_shared( - mWindow, Utils::String::toUpper(ViewController::KEYBOARD_CHAR + " GAME NAME"), - Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + mWindow, Utils::String::toUpper(ViewController::KEYBOARD_CHAR + " GAME NAME"), + Font::get(FONT_SIZE_MEDIUM), 0x777777FF); mTextFilterField = std::make_shared(mWindow, "", Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT); @@ -121,7 +123,7 @@ void GuiGamelistFilter::addFiltersToMenu() { } // Callback function. - auto updateVal = [this](const std::string &newVal) { + auto updateVal = [this](const std::string& newVal) { mTextFilterField->setValue(Utils::String::toUpper(newVal)); mFilterIndex->setTextFilter(Utils::String::toUpper(newVal)); }; @@ -132,7 +134,8 @@ void GuiGamelistFilter::addFiltersToMenu() { mTextFilterField->getValue(), updateVal, false, "OK", "APPLY CHANGES?")); }); - } else { + } + else { row.makeAcceptInputHandler([this, updateVal] { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "GAME NAME", mTextFilterField->getValue(), updateVal, false, diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index c63e38da0..b7cbbd1cb 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -25,9 +25,15 @@ #include "views/ViewController.h" #include "views/gamelist/IGameListView.h" -GuiGamelistOptions::GuiGamelistOptions(Window *window, SystemData *system) - : GuiComponent(window), mMenu(window, "OPTIONS"), mSystem(system), mFiltersChanged(false), mCancelled(false), - mIsCustomCollection(false), mIsCustomCollectionGroup(false), mCustomCollectionSystem(nullptr) +GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) + : GuiComponent(window) + , mMenu(window, "OPTIONS") + , mSystem(system) + , mFiltersChanged(false) + , mCancelled(false) + , mIsCustomCollection(false) + , mIsCustomCollectionGroup(false) + , mCustomCollectionSystem(nullptr) { addChild(&mMenu); diff --git a/es-app/src/guis/GuiLaunchScreen.cpp b/es-app/src/guis/GuiLaunchScreen.cpp index ce347b85f..bffcefb71 100644 --- a/es-app/src/guis/GuiLaunchScreen.cpp +++ b/es-app/src/guis/GuiLaunchScreen.cpp @@ -14,9 +14,12 @@ #include "components/TextComponent.h" #include "utils/StringUtil.h" -GuiLaunchScreen::GuiLaunchScreen(Window *window) - : GuiComponent(window), mWindow(window), mBackground(window, ":/graphics/frame.svg"), mGrid(nullptr), - mMarquee(nullptr) +GuiLaunchScreen::GuiLaunchScreen(Window* window) + : GuiComponent(window) + , mWindow(window) + , mBackground(window, ":/graphics/frame.svg") + , mGrid(nullptr) + , mMarquee(nullptr) { addChild(&mBackground); mWindow->setLaunchScreen(this); @@ -217,7 +220,8 @@ void GuiLaunchScreen::update(int deltaTime) mScaleUp = glm::clamp(mScaleUp + 0.07f, 0.0f, 1.0f); } -void GuiLaunchScreen::render(const glm::mat4 & /*parentTrans*/) { +void GuiLaunchScreen::render(const glm::mat4& /*parentTrans*/) +{ // Scale up animation. if (mScaleUp < 1.0f) setScale(mScaleUp); diff --git a/es-app/src/guis/GuiLaunchScreen.h b/es-app/src/guis/GuiLaunchScreen.h index 352bfa711..dac32f24f 100644 --- a/es-app/src/guis/GuiLaunchScreen.h +++ b/es-app/src/guis/GuiLaunchScreen.h @@ -21,11 +21,11 @@ class FileData; class GuiLaunchScreen : public Window::GuiLaunchScreen, GuiComponent { public: - GuiLaunchScreen(Window *window); + GuiLaunchScreen(Window* window); virtual ~GuiLaunchScreen(); - virtual void displayLaunchScreen(FileData *game) override; + virtual void displayLaunchScreen(FileData* game) override; virtual void closeLaunchScreen() override; @@ -33,12 +33,12 @@ public: virtual void update(int deltaTime) override; - virtual void render(const glm::mat4 &parentTrans) override; + virtual void render(const glm::mat4& parentTrans) override; private: - Window *mWindow; + Window* mWindow; NinePatchComponent mBackground; - ComponentGrid *mGrid; + ComponentGrid* mGrid; std::shared_ptr mTitle; std::shared_ptr mGameName; diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 23b17a8eb..63090620b 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -36,8 +36,10 @@ #include #include -GuiMenu::GuiMenu(Window *window) - : GuiComponent(window), mMenu(window, "MAIN MENU"), mVersion(window) +GuiMenu::GuiMenu(Window* window) + : GuiComponent(window) + , mMenu(window, "MAIN MENU") + , mVersion(window) { bool isFullUI = UIModeController::getInstance()->isUIModeFull(); @@ -822,16 +824,17 @@ void GuiMenu::openOtherOptions() multiLineMediaDir] { if (Settings::getInstance()->getBool("VirtualKeyboard")) { mWindow->pushGui(new GuiTextEditKeyboardPopup( - mWindow, getHelpStyle(), titleMediaDir, - Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, - multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, - defaultDirectoryText, "load default directory")); - } else { + mWindow, getHelpStyle(), titleMediaDir, + Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, + multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, + defaultDirectoryText, "load default directory")); + } + else { mWindow->pushGui(new GuiTextEditPopup( - mWindow, getHelpStyle(), titleMediaDir, - Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, - multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, - defaultDirectoryText, "load default directory")); + mWindow, getHelpStyle(), titleMediaDir, + Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, + multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText, + defaultDirectoryText, "load default directory")); } }); s->addRow(rowMediaDir); diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 2aa7a5213..1c73698ab 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -31,18 +31,25 @@ #include "utils/StringUtil.h" #include "views/ViewController.h" -GuiMetaDataEd::GuiMetaDataEd(Window *window, - MetaDataList *md, - const std::vector &mdd, +GuiMetaDataEd::GuiMetaDataEd(Window* window, + MetaDataList* md, + const std::vector& mdd, ScraperSearchParams scraperParams, - const std::string & /*header*/, + const std::string& /*header*/, std::function saveCallback, std::function clearGameFunc, std::function deleteGameFunc) - : GuiComponent(window), mBackground(window, ":/graphics/frame.svg"), mGrid(window, glm::ivec2{1, 3}), - mScraperParams(scraperParams), mMetaDataDecl(mdd), mMetaData(md), mSavedCallback(saveCallback), - mClearGameFunc(clearGameFunc), mDeleteGameFunc(deleteGameFunc), mMediaFilesUpdated(false), - mInvalidEmulatorEntry(false) + : GuiComponent(window) + , mBackground(window, ":/graphics/frame.svg") + , mGrid(window, glm::ivec2{1, 3}) + , mScraperParams(scraperParams) + , mMetaDataDecl(mdd) + , mMetaData(md) + , mSavedCallback(saveCallback) + , mClearGameFunc(clearGameFunc) + , mDeleteGameFunc(deleteGameFunc) + , mMediaFilesUpdated(false) + , mInvalidEmulatorEntry(false) { addChild(&mBackground); addChild(&mGrid); @@ -211,11 +218,11 @@ GuiMetaDataEd::GuiMetaDataEd(Window *window, if (mInvalidEmulatorEntry || scraperParams.system->getSystemEnvData()->mLaunchCommands.size() > 1) { row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal, - originalValue] { - GuiSettings *s = nullptr; + originalValue] { + GuiSettings* s = nullptr; bool singleEntry = - scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1; + scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1; if (mInvalidEmulatorEntry && singleEntry) s = new GuiSettings(mWindow, "CLEAR INVALID ENTRY"); @@ -226,16 +233,16 @@ GuiMetaDataEd::GuiMetaDataEd(Window *window, return; std::vector> launchCommands = - scraperParams.system->getSystemEnvData()->mLaunchCommands; + scraperParams.system->getSystemEnvData()->mLaunchCommands; if (ed->getValue() != "" && mInvalidEmulatorEntry && singleEntry) launchCommands.push_back(std::make_pair( - "", ViewController::EXCLAMATION_CHAR + " " + originalValue)); + "", ViewController::EXCLAMATION_CHAR + " " + originalValue)); else if (ed->getValue() != "") launchCommands.push_back(std::make_pair( - "", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY")); + "", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY")); - for (auto entry: launchCommands) { + for (auto entry : launchCommands) { std::string selectedLabel = ed->getValue(); std::string label; ComponentListRow row; @@ -356,7 +363,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window *window, ed->setColor(DEFAULT_TEXTCOLOR); else ed->setColor(TEXTCOLOR_USERMARKED); - } else { + } + else { ed->setValue(newVal); if (newVal == originalValue) ed->setColor(DEFAULT_TEXTCOLOR); @@ -368,10 +376,11 @@ GuiMetaDataEd::GuiMetaDataEd(Window *window, if (Settings::getInstance()->getBool("VirtualKeyboard")) { row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { mWindow->pushGui(new GuiTextEditKeyboardPopup( - mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine, - "apply", "APPLY CHANGES?", "", "")); + mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine, + "apply", "APPLY CHANGES?", "", "")); }); - } else { + } + else { row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine, diff --git a/es-app/src/guis/GuiOfflineGenerator.cpp b/es-app/src/guis/GuiOfflineGenerator.cpp index 2f214f987..d88e67932 100644 --- a/es-app/src/guis/GuiOfflineGenerator.cpp +++ b/es-app/src/guis/GuiOfflineGenerator.cpp @@ -13,9 +13,11 @@ #include "components/MenuComponent.h" #include "views/ViewController.h" -GuiOfflineGenerator::GuiOfflineGenerator(Window *window, const std::queue &gameQueue) - : GuiComponent(window), mGameQueue(gameQueue), mBackground(window, ":/graphics/frame.svg"), - mGrid(window, glm::ivec2{6, 13}) +GuiOfflineGenerator::GuiOfflineGenerator(Window* window, const std::queue& gameQueue) + : GuiComponent(window) + , mGameQueue(gameQueue) + , mBackground(window, ":/graphics/frame.svg") + , mGrid(window, glm::ivec2{6, 13}) { addChild(&mBackground); addChild(&mGrid); diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp index 6f7ec1923..067352199 100644 --- a/es-app/src/guis/GuiScraperMulti.cpp +++ b/es-app/src/guis/GuiScraperMulti.cpp @@ -86,19 +86,19 @@ GuiScraperMulti::GuiScraperMulti(Window* window, // Previously refined. if (mSearchComp->getRefinedSearch()) allowRefine = true; - // Interactive mode and "Auto-accept single game matches" not enabled. + // Interactive mode and "Auto-accept single game matches" not enabled. else if (mSearchComp->getSearchType() != GuiScraperSearch::ACCEPT_SINGLE_MATCHES) allowRefine = true; - // Interactive mode with "Auto-accept single game matches" enabled and more - // than one result. + // Interactive mode with "Auto-accept single game matches" enabled and more + // than one result. else if (mSearchComp->getSearchType() == - GuiScraperSearch::ACCEPT_SINGLE_MATCHES && + GuiScraperSearch::ACCEPT_SINGLE_MATCHES && mSearchComp->getScraperResultsSize() > 1) allowRefine = true; - // Dito but there were no games found, or the search has not been completed. + // Dito but there were no games found, or the search has not been completed. else if (mSearchComp->getSearchType() == - GuiScraperSearch::ACCEPT_SINGLE_MATCHES && + GuiScraperSearch::ACCEPT_SINGLE_MATCHES && !mSearchComp->getFoundGame()) allowRefine = true; diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 1f5b9a1c9..d749c1302 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -37,9 +37,15 @@ #define FAILED_VERIFICATION_RETRIES 8 -GuiScraperSearch::GuiScraperSearch(Window *window, SearchType type, unsigned int scrapeCount) - : GuiComponent(window), mGrid(window, glm::ivec2{4, 3}), mSearchType(type), mScrapeCount(scrapeCount), - mRefinedSearch(false), mFoundGame(false), mScrapeRatings(false), mBusyAnim(window) +GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int scrapeCount) + : GuiComponent(window) + , mGrid(window, glm::ivec2{4, 3}) + , mSearchType(type) + , mScrapeCount(scrapeCount) + , mRefinedSearch(false) + , mFoundGame(false) + , mScrapeRatings(false) + , mBusyAnim(window) { addChild(&mGrid); @@ -467,14 +473,14 @@ void GuiScraperSearch::updateInfoPane() i = 0; if (i != -1 && static_cast(mScraperResults.size()) > i) { - ScraperSearchResult &res = mScraperResults.at(i); + ScraperSearchResult& res = mScraperResults.at(i); mResultName->setText(Utils::String::toUpper(res.mdl.get("name"))); mResultDesc->setText(Utils::String::toUpper(res.mdl.get("desc"))); mDescContainer->reset(); mResultThumbnail->setImage(""); - const std::string &thumb = res.screenshotUrl.empty() ? res.coverUrl : res.screenshotUrl; + const std::string& thumb = res.screenshotUrl.empty() ? res.coverUrl : res.screenshotUrl; mScraperResults[i].thumbnailImageUrl = thumb; // Cache the thumbnail image in mScraperResults so that we don't need to download @@ -547,13 +553,13 @@ bool GuiScraperSearch::input(InputConfig* config, Input input) // Previously refined. if (mRefinedSearch) allowRefine = true; - // Interactive mode and "Auto-accept single game matches" not enabled. + // Interactive mode and "Auto-accept single game matches" not enabled. else if (mSearchType != ACCEPT_SINGLE_MATCHES) allowRefine = true; - // Interactive mode with "Auto-accept single game matches" enabled and more than one result. + // Interactive mode with "Auto-accept single game matches" enabled and more than one result. else if (mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1) allowRefine = true; - // Dito but there were no games found, or the search has not been completed. + // Dito but there were no games found, or the search has not been completed. else if (mSearchType == ACCEPT_SINGLE_MATCHES && !mFoundGame) allowRefine = true; @@ -784,15 +790,16 @@ void GuiScraperSearch::updateThumbnail() } } -void GuiScraperSearch::openInputScreen(ScraperSearchParams ¶ms) { +void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) +{ auto searchForFunc = [&](std::string name) { // Trim leading and trailing whitespaces. name.erase(name.begin(), std::find_if(name.begin(), name.end(), [](char c) { - return !std::isspace(static_cast(c)); - })); + return !std::isspace(static_cast(c)); + })); name.erase(std::find_if(name.rbegin(), name.rend(), [](char c) { return !std::isspace(static_cast(c)); }) - .base(), + .base(), name.end()); stop(); @@ -810,7 +817,8 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams ¶ms) { // regardless of whether the entry is an arcade game and TheGamesDB is used. if (Settings::getInstance()->getBool("ScraperSearchMetadataName")) { searchString = Utils::String::removeParenthesis(params.game->metadata.get("name")); - } else { + } + else { // If searching based on the actual file name, then expand to the full game name // in case the scraper is set to TheGamesDB and it's an arcade game. This is // required as TheGamesDB does not support searches using the short MAME names. @@ -820,7 +828,8 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams ¶ms) { else searchString = params.game->getCleanName(); } - } else { + } + else { searchString = params.nameOverride; } @@ -828,7 +837,8 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams ¶ms) { mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), "REFINE SEARCH", searchString, searchForFunc, false, "SEARCH", "SEARCH USING REFINED NAME?")); - } else { + } + else { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH", searchString, searchForFunc, false, "SEARCH", "SEARCH USING REFINED NAME?")); @@ -918,7 +928,8 @@ bool GuiScraperSearch::saveMetadata(const ScraperSearchResult& result, return metadataUpdated; } -std::vector GuiScraperSearch::getHelpPrompts() { +std::vector GuiScraperSearch::getHelpPrompts() +{ std::vector prompts; prompts.push_back(HelpPrompt("y", "refine search")); diff --git a/es-app/src/guis/GuiScraperSearch.h b/es-app/src/guis/GuiScraperSearch.h index 0cf1dfd3b..fb824360e 100644 --- a/es-app/src/guis/GuiScraperSearch.h +++ b/es-app/src/guis/GuiScraperSearch.h @@ -64,19 +64,21 @@ public: mAcceptCallback = acceptCallback; } - void setSkipCallback(const std::function &skipCallback) { + void setSkipCallback(const std::function& skipCallback) + { mSkipCallback = skipCallback; } - void setCancelCallback(const std::function &cancelCallback) { + void setCancelCallback(const std::function& cancelCallback) + { mCancelCallback = cancelCallback; } - bool input(InputConfig *config, Input input) override; + bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; - void render(const glm::mat4 &parentTrans) override; + void render(const glm::mat4& parentTrans) override; std::vector getHelpPrompts() override; @@ -84,7 +86,8 @@ public: void onSizeChanged() override; - void decreaseScrapeCount() { + void decreaseScrapeCount() + { if (mScrapeCount > 0) mScrapeCount--; } @@ -95,7 +98,7 @@ public: bool getFoundGame() { return mFoundGame; } - const std::string &getNameOverride() { return mLastSearch.nameOverride; } + const std::string& getNameOverride() { return mLastSearch.nameOverride; } void onFocusGained() override { mGrid.onFocusGained(); } diff --git a/es-app/src/guis/GuiSettings.cpp b/es-app/src/guis/GuiSettings.cpp index cbfbd2197..6cd181dc8 100644 --- a/es-app/src/guis/GuiSettings.cpp +++ b/es-app/src/guis/GuiSettings.cpp @@ -21,11 +21,21 @@ #include "views/ViewController.h" #include "views/gamelist/IGameListView.h" -GuiSettings::GuiSettings(Window *window, std::string title) - : GuiComponent(window), mMenu(window, title), mGoToSystem(nullptr), mNeedsSaving(false), - mNeedsReloadHelpPrompts(false), mNeedsCollectionsUpdate(false), mNeedsSorting(false), - mNeedsSortingCollections(false), mNeedsResetFilters(false), mNeedsReloading(false), mNeedsGoToStart(false), - mNeedsGoToSystem(false), mNeedsGoToGroupedCollections(false), mInvalidateCachedBackground(false) +GuiSettings::GuiSettings(Window* window, std::string title) + : GuiComponent(window) + , mMenu(window, title) + , mGoToSystem(nullptr) + , mNeedsSaving(false) + , mNeedsReloadHelpPrompts(false) + , mNeedsCollectionsUpdate(false) + , mNeedsSorting(false) + , mNeedsSortingCollections(false) + , mNeedsResetFilters(false) + , mNeedsReloading(false) + , mNeedsGoToStart(false) + , mNeedsGoToSystem(false) + , mNeedsGoToGroupedCollections(false) + , mInvalidateCachedBackground(false) { addChild(&mMenu); mMenu.addButton("BACK", "back", [this] { delete this; }); @@ -174,10 +184,12 @@ void GuiSettings::addEditableTextComponent(const std::string label, else if (isPassword && newVal == "") { ed->setValue(""); ed->setHiddenValue(""); - } else if (isPassword) { + } + else if (isPassword) { ed->setValue("********"); ed->setHiddenValue(newVal); - } else { + } + else { ed->setValue(newVal); } }; @@ -187,13 +199,14 @@ void GuiSettings::addEditableTextComponent(const std::string label, // Never display the value if it's a password, instead set it to blank. if (isPassword) mWindow->pushGui(new GuiTextEditKeyboardPopup( - mWindow, getHelpStyle(), label, "", updateVal, false, "SAVE", "SAVE CHANGES?")); + mWindow, getHelpStyle(), label, "", updateVal, false, "SAVE", "SAVE CHANGES?")); else mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), label, ed->getValue(), updateVal, false, "SAVE", "SAVE CHANGES?")); }); - } else { + } + else { row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { if (isPassword) mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, "", updateVal, diff --git a/es-app/src/scrapers/GamesDBJSONScraper.cpp b/es-app/src/scrapers/GamesDBJSONScraper.cpp index 87dedbac7..4c015d38c 100644 --- a/es-app/src/scrapers/GamesDBJSONScraper.cpp +++ b/es-app/src/scrapers/GamesDBJSONScraper.cpp @@ -147,7 +147,8 @@ void thegamesdb_generate_json_scraper_requests( // using this regardless of whether the entry is an arcade game. if (Settings::getInstance()->getBool("ScraperSearchMetadataName")) { cleanName = Utils::String::removeParenthesis(params.game->metadata.get("name")); - } else { + } + else { // If not searching based on the metadata name, then check whether it's an // arcade game and if so expand to the full game name. This is required as // TheGamesDB has issues with searching using the short MAME names. @@ -164,10 +165,10 @@ void thegamesdb_generate_json_scraper_requests( return !std::isspace(static_cast(c)); })); cleanName.erase( - std::find_if(cleanName.rbegin(), cleanName.rend(), - [](char c) { return !std::isspace(static_cast(c)); }) - .base(), - cleanName.end()); + std::find_if(cleanName.rbegin(), cleanName.rend(), + [](char c) { return !std::isspace(static_cast(c)); }) + .base(), + cleanName.end()); path += "/Games/ByGameName?" + apiKey + "&fields=players,publishers,genres,overview,last_updated,rating," @@ -456,7 +457,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr& req, if (doc.HasMember("remaining_monthly_allowance") && doc.HasMember("extra_allowance")) { for (size_t i = 0; i < results.size(); i++) { results[i].scraperRequestAllowance = - doc["remaining_monthly_allowance"].GetInt() + doc["extra_allowance"].GetInt(); + doc["remaining_monthly_allowance"].GetInt() + doc["extra_allowance"].GetInt(); } LOG(LogDebug) << "TheGamesDBJSONRequest::process(): " "Remaining monthly scraping allowance: " diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index c62eef1b1..736f1ac76 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -17,12 +17,32 @@ #define FADE_IN_TIME 650 DetailedGameListView::DetailedGameListView(Window* window, FileData* root) - : BasicGameListView(window, root), mThumbnail(window), mMarquee(window), mImage(window), mLblRating(window), - mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window), mLblGenre(window), mLblPlayers(window), - mLblLastPlayed(window), mLblPlayCount(window), mBadges(window), mRating(window), mReleaseDate(window), - mDeveloper(window), - mPublisher(window), mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window), mName(window), - mDescContainer(window), mDescription(window), mGamelistInfo(window), mLastUpdated(nullptr) + : BasicGameListView(window, root) + , mThumbnail(window) + , mMarquee(window) + , mImage(window) + , mLblRating(window) + , mLblReleaseDate(window) + , mLblDeveloper(window) + , mLblPublisher(window) + , mLblGenre(window) + , mLblPlayers(window) + , mLblLastPlayed(window) + , mLblPlayCount(window) + , mBadges(window) + , mRating(window) + , mReleaseDate(window) + , mDeveloper(window) + , mPublisher(window) + , mGenre(window) + , mPlayers(window) + , mLastPlayed(window) + , mPlayCount(window) + , mName(window) + , mDescContainer(window) + , mDescription(window) + , mGamelistInfo(window) + , mLastUpdated(nullptr) { const float padding = 0.01f; @@ -111,7 +131,8 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) initMDValues(); } -void DetailedGameListView::onThemeChanged(const std::shared_ptr& theme) { +void DetailedGameListView::onThemeChanged(const std::shared_ptr& theme) +{ BasicGameListView::onThemeChanged(theme); using namespace ThemeFlags; @@ -124,21 +145,21 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr& them mName.applyTheme(theme, getName(), "md_name", ALL); initMDLabels(); - std::vector labels = getMDLabels(); + std::vector labels = getMDLabels(); assert(labels.size() == 8); std::vector lblElements = { - "md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher", - "md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"}; + "md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher", + "md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"}; for (unsigned int i = 0; i < labels.size(); i++) labels[i]->applyTheme(theme, getName(), lblElements[i], ALL); initMDValues(); - std::vector values = getMDValues(); + std::vector values = getMDValues(); assert(values.size() == 9); - std::vector valElements = {"md_rating", "md_releasedate", "md_developer", - "md_publisher", "md_genre", "md_players", - "md_badges", "md_lastplayed", "md_playcount"}; + std::vector valElements = {"md_rating", "md_releasedate", "md_developer", + "md_publisher", "md_genre", "md_players", + "md_badges", "md_lastplayed", "md_playcount"}; for (unsigned int i = 0; i < values.size(); i++) values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); @@ -402,7 +423,8 @@ void DetailedGameListView::updateInfoPanel() mLastPlayed.setValue(file->metadata.get("lastplayed")); mPlayCount.setValue(file->metadata.get("playcount")); } - } else if (file->getType() == FOLDER) { + } + else if (file->getType() == FOLDER) { if (!hideMetaDataFields) { mLastPlayed.setValue(file->metadata.get("lastplayed")); mLblPlayCount.setVisible(false); diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index 319029bce..4f9491f8a 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -20,12 +20,31 @@ #define FADE_IN_TIME 650 GridGameListView::GridGameListView(Window* window, FileData* root) - : ISimpleGameListView(window, root), mGrid(window), mMarquee(window), mImage(window), mLblRating(window), - mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window), mLblGenre(window), mLblPlayers(window), - mLblLastPlayed(window), mLblPlayCount(window), mBadges(window), mRating(window), mReleaseDate(window), - mDeveloper(window), - mPublisher(window), mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window), mName(window), - mDescContainer(window), mDescription(window), mGamelistInfo(window) + : ISimpleGameListView(window, root) + , mGrid(window) + , mMarquee(window) + , mImage(window) + , mLblRating(window) + , mLblReleaseDate(window) + , mLblDeveloper(window) + , mLblPublisher(window) + , mLblGenre(window) + , mLblPlayers(window) + , mLblLastPlayed(window) + , mLblPlayCount(window) + , mBadges(window) + , mRating(window) + , mReleaseDate(window) + , mDeveloper(window) + , mPublisher(window) + , mGenre(window) + , mPlayers(window) + , mLastPlayed(window) + , mPlayCount(window) + , mName(window) + , mDescContainer(window) + , mDescription(window) + , mGamelistInfo(window) { const float padding = 0.01f; diff --git a/es-app/src/views/gamelist/GridGameListView.h b/es-app/src/views/gamelist/GridGameListView.h index 7fa25bce0..b89852271 100644 --- a/es-app/src/views/gamelist/GridGameListView.h +++ b/es-app/src/views/gamelist/GridGameListView.h @@ -68,12 +68,12 @@ protected: ImageGridComponent mGrid; // Points to the first game in the list, i.e. the first entry which is of the type 'GAME'. - FileData *firstGameEntry; + FileData* firstGameEntry; private: void updateInfoPanel(); - const std::string getImagePath(FileData *file); + const std::string getImagePath(FileData* file); void initMDLabels(); diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index 170a0dbbd..0cd36f4e7 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -20,7 +20,7 @@ class VideoComponent; class VideoGameListView : public BasicGameListView { public: - VideoGameListView(Window *window, FileData *root); + VideoGameListView(Window* window, FileData* root); virtual ~VideoGameListView() noexcept; diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index 5790a3cb0..5075fc311 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -16,11 +16,22 @@ #include -GuiComponent::GuiComponent(Window *window) - : mWindow(window), mParent(nullptr), mOpacity(255), mColor(0), mSaturation(1.0f), mColorShift(0), - mColorShiftEnd(0), - mPosition({}), mOrigin({}), mRotationOrigin(0.5f, 0.5f), mSize({}), mIsProcessing(false), mVisible(true), - mEnabled(true), mTransform(Renderer::getIdentity()) +GuiComponent::GuiComponent(Window* window) + : mWindow(window) + , mParent(nullptr) + , mOpacity(255) + , mColor(0) + , mSaturation(1.0f) + , mColorShift(0) + , mColorShiftEnd(0) + , mPosition({}) + , mOrigin({}) + , mRotationOrigin(0.5f, 0.5f) + , mSize({}) + , mIsProcessing(false) + , mVisible(true) + , mEnabled(true) + , mTransform(Renderer::getIdentity()) { for (unsigned char i = 0; i < MAX_ANIMATIONS; i++) mAnimationMap[i] = nullptr; diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 2e7efd9a5..7bb184c14 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -37,7 +37,7 @@ class Window; class GuiComponent { public: - GuiComponent(Window *window); + GuiComponent(Window* window); virtual ~GuiComponent() noexcept; @@ -231,15 +231,15 @@ public: const static unsigned char MAX_ANIMATIONS = 4; protected: - void renderChildren(const glm::mat4 &transform) const; + void renderChildren(const glm::mat4& transform) const; void updateSelf(int deltaTime); // Updates animations. void updateChildren(int deltaTime); // Updates animations. - Window *mWindow; + Window* mWindow; - GuiComponent *mParent; - std::vector mChildren; + GuiComponent* mParent; + std::vector mChildren; unsigned char mOpacity; unsigned int mColor; diff --git a/es-core/src/HttpReq.h b/es-core/src/HttpReq.h index 78feb529a..d0b9f38ee 100644 --- a/es-core/src/HttpReq.h +++ b/es-core/src/HttpReq.h @@ -80,7 +80,7 @@ private: static CURLM* s_multi_handle; Status mStatus; - CURL *mHandle; + CURL* mHandle; std::stringstream mContent; std::string mErrorMsg; diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 5fb20c41c..e60471bb7 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -14,11 +14,12 @@ #include "resources/TextureResource.h" // Available slot definitions. -std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN, - SLOT_ALTERNATIVE_EMULATOR}; +std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, + SLOT_BROKEN, SLOT_ALTERNATIVE_EMULATOR}; -BadgesComponent::BadgesComponent(Window *window) - : FlexboxComponent(window) { +BadgesComponent::BadgesComponent(Window* window) + : FlexboxComponent(window) +{ mBadgeIcons = std::map(); mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; @@ -45,16 +46,17 @@ BadgesComponent::BadgesComponent(Window *window) mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); } -BadgesComponent::~BadgesComponent() noexcept { - for (GuiComponent *c: mChildren) +BadgesComponent::~BadgesComponent() noexcept +{ + for (GuiComponent* c : mChildren) c->clearChildren(); clearChildren(); mBadgeIcons.clear(); mImageComponents.clear(); } - -void BadgesComponent::setValue(const std::string &value) { +void BadgesComponent::setValue(const std::string& value) +{ mChildren.clear(); if (!value.empty()) { std::string temp; @@ -94,7 +96,8 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, bool imgChanged = false; for (auto& slot : mSlots) { - if (properties & PATH && elem->has(slot) && mBadgeIcons[slot] != elem->get(slot)) { + if (properties & PATH && elem->has(slot) && + mBadgeIcons[slot] != elem->get(slot)) { mBadgeIcons[slot] = elem->get(slot); mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot], false, true, true); imgChanged = true; diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index ce158012b..27a642738 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -27,16 +27,16 @@ class TextureResource; class BadgesComponent : public FlexboxComponent { public: - BadgesComponent(Window *window); + BadgesComponent(Window* window); ~BadgesComponent() noexcept; std::string getValue() const override; // Should be a list of strings. - void setValue(const std::string &value) override; + void setValue(const std::string& value) override; - virtual void applyTheme(const std::shared_ptr &theme, - const std::string &view, - const std::string &element, + virtual void applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, unsigned int properties) override; virtual std::vector getHelpPrompts() override; diff --git a/es-core/src/components/ButtonComponent.cpp b/es-core/src/components/ButtonComponent.cpp index 94734a0b7..d7ffd4d23 100644 --- a/es-core/src/components/ButtonComponent.cpp +++ b/es-core/src/components/ButtonComponent.cpp @@ -12,15 +12,24 @@ #include "resources/Font.h" #include "utils/StringUtil.h" -ButtonComponent::ButtonComponent(Window *window, - const std::string &text, - const std::string &helpText, - const std::function &func, +ButtonComponent::ButtonComponent(Window* window, + const std::string& text, + const std::string& helpText, + const std::function& func, bool upperCase, bool flatStyle) - : GuiComponent{window}, mBox{window, ":/graphics/button.svg"}, mFont{Font::get(FONT_SIZE_MEDIUM)}, mPadding{{}}, - mFocused{false}, mEnabled{true}, mFlatStyle{flatStyle}, mTextColorFocused{0xFFFFFFFF}, - mTextColorUnfocused{0x777777FF}, mFlatColorFocused{0x878787FF}, mFlatColorUnfocused{0x60606025} { + : GuiComponent{window} + , mBox{window, ":/graphics/button.svg"} + , mFont{Font::get(FONT_SIZE_MEDIUM)} + , mPadding{{}} + , mFocused{false} + , mEnabled{true} + , mFlatStyle{flatStyle} + , mTextColorFocused{0xFFFFFFFF} + , mTextColorUnfocused{0x777777FF} + , mFlatColorFocused{0x878787FF} + , mFlatColorUnfocused{0x60606025} +{ setPressedFunc(func); setText(text, helpText, upperCase); @@ -28,7 +37,8 @@ ButtonComponent::ButtonComponent(Window *window, updateImage(); } -void ButtonComponent::onSizeChanged() { +void ButtonComponent::onSizeChanged() +{ if (mFlatStyle) return; @@ -39,24 +49,27 @@ void ButtonComponent::onSizeChanged() { glm::vec2{-cornerSize.x * 2.0f, -cornerSize.y * 2.0f}); } -void ButtonComponent::onFocusGained() { +void ButtonComponent::onFocusGained() +{ mFocused = true; if (!mFlatStyle) updateImage(); } -void ButtonComponent::onFocusLost() { +void ButtonComponent::onFocusLost() +{ mFocused = false; if (!mFlatStyle) updateImage(); } -void ButtonComponent::setText(const std::string &text, const std::string &helpText, bool upperCase) { +void ButtonComponent::setText(const std::string& text, const std::string& helpText, bool upperCase) +{ mText = upperCase ? Utils::String::toUpper(text) : text; mHelpText = helpText; mTextCache = - std::unique_ptr(mFont->buildTextCache(mText, 0.0f, 0.0f, getCurTextColor())); + std::unique_ptr(mFont->buildTextCache(mText, 0.0f, 0.0f, getCurTextColor())); float minWidth = mFont->sizeText("DELETE").x + (12.0f * Renderer::getScreenWidthModifier()); setSize(std::max(mTextCache->metrics.size.x + (12.0f * Renderer::getScreenWidthModifier()), @@ -66,13 +79,15 @@ void ButtonComponent::setText(const std::string &text, const std::string &helpTe updateHelpPrompts(); } -void ButtonComponent::setEnabled(bool state) { +void ButtonComponent::setEnabled(bool state) +{ mEnabled = state; if (!mFlatStyle) updateImage(); } -void ButtonComponent::setPadding(const glm::vec4 padding) { +void ButtonComponent::setPadding(const glm::vec4 padding) +{ if (mPadding == padding) return; @@ -80,7 +95,8 @@ void ButtonComponent::setPadding(const glm::vec4 padding) { onSizeChanged(); } -bool ButtonComponent::input(InputConfig *config, Input input) { +bool ButtonComponent::input(InputConfig* config, Input input) +{ if (config->isMappedTo("a", input) && input.value != 0) { if (mPressedFunc && mEnabled) mPressedFunc(); @@ -90,7 +106,8 @@ bool ButtonComponent::input(InputConfig *config, Input input) { return GuiComponent::input(config, input); } -void ButtonComponent::render(const glm::mat4 &parentTrans) { +void ButtonComponent::render(const glm::mat4& parentTrans) +{ glm::mat4 trans{parentTrans * getTransform()}; if (mFlatStyle) { @@ -99,13 +116,15 @@ void ButtonComponent::render(const glm::mat4 &parentTrans) { Renderer::drawRect(mPadding.x, mPadding.y, mSize.x - mPadding.x - mPadding.z, mSize.y - mPadding.y - mPadding.w, mFlatColorFocused, mFlatColorFocused); - } else { + } + else { Renderer::setMatrix(trans); Renderer::drawRect(mPadding.x, mPadding.y, mSize.x - mPadding.x - mPadding.z, mSize.y - mPadding.y - mPadding.w, mFlatColorUnfocused, mFlatColorUnfocused); } - } else { + } + else { mBox.render(trans); } @@ -131,20 +150,23 @@ void ButtonComponent::render(const glm::mat4 &parentTrans) { renderChildren(trans); } -std::vector ButtonComponent::getHelpPrompts() { +std::vector ButtonComponent::getHelpPrompts() +{ std::vector prompts; prompts.push_back(HelpPrompt("a", mHelpText.empty() ? mText.c_str() : mHelpText.c_str())); return prompts; } -unsigned int ButtonComponent::getCurTextColor() const { +unsigned int ButtonComponent::getCurTextColor() const +{ if (!mFocused) return mTextColorUnfocused; else return mTextColorFocused; } -void ButtonComponent::updateImage() { +void ButtonComponent::updateImage() +{ if (!mEnabled || !mPressedFunc) { mBox.setImagePath(":/graphics/button_filled.svg"); mBox.setCenterColor(0x770000FF); diff --git a/es-core/src/components/ButtonComponent.h b/es-core/src/components/ButtonComponent.h index 7b1dad9a3..3affa67f2 100644 --- a/es-core/src/components/ButtonComponent.h +++ b/es-core/src/components/ButtonComponent.h @@ -14,12 +14,13 @@ class TextCache; -class ButtonComponent : public GuiComponent { +class ButtonComponent : public GuiComponent +{ public: - ButtonComponent(Window *window, - const std::string &text = "", - const std::string &helpText = "", - const std::function &func = nullptr, + ButtonComponent(Window* window, + const std::string& text = "", + const std::string& helpText = "", + const std::function& func = nullptr, bool upperCase = true, bool flatStyle = false); @@ -29,9 +30,9 @@ public: void onFocusLost() override; - void setText(const std::string &text, const std::string &helpText, bool upperCase = true); + void setText(const std::string& text, const std::string& helpText, bool upperCase = true); - const std::string &getText() const { return mText; } + const std::string& getText() const { return mText; } void setPressedFunc(std::function f) { mPressedFunc = f; } @@ -45,11 +46,11 @@ public: void setFlatColorUnfocused(unsigned int color) { mFlatColorUnfocused = color; } - const std::function &getPressedFunc() const { return mPressedFunc; } + const std::function& getPressedFunc() const { return mPressedFunc; } - bool input(InputConfig *config, Input input) override; + bool input(InputConfig* config, Input input) override; - void render(const glm::mat4 &parentTrans) override; + void render(const glm::mat4& parentTrans) override; virtual std::vector getHelpPrompts() override; diff --git a/es-core/src/components/ComponentGrid.cpp b/es-core/src/components/ComponentGrid.cpp index 3f73e93c0..bd6012d12 100644 --- a/es-core/src/components/ComponentGrid.cpp +++ b/es-core/src/components/ComponentGrid.cpp @@ -245,7 +245,7 @@ const ComponentGrid::GridEntry* ComponentGrid::getCellAt(int x, int y) const bool ComponentGrid::input(InputConfig* config, Input input) { - const GridEntry *cursorEntry = getCellAt(mCursor); + const GridEntry* cursorEntry = getCellAt(mCursor); if (cursorEntry && cursorEntry->component->input(config, input)) return true; @@ -287,11 +287,12 @@ void ComponentGrid::resetCursor() } } -bool ComponentGrid::moveCursor(glm::ivec2 dir) { +bool ComponentGrid::moveCursor(glm::ivec2 dir) +{ assert(dir.x || dir.y); const glm::ivec2 origCursor{mCursor}; - const GridEntry *currentCursorEntry = getCellAt(mCursor); + const GridEntry* currentCursorEntry = getCellAt(mCursor); glm::ivec2 searchAxis(dir.x == 0, dir.y == 0); // Logic to handle entries that span several cells. @@ -325,7 +326,7 @@ bool ComponentGrid::moveCursor(glm::ivec2 dir) { while (mCursor.x >= 0 && mCursor.y >= 0 && mCursor.x < mGridSize.x && mCursor.y < mGridSize.y) { mCursor = mCursor + dir; glm::ivec2 curDirPos{mCursor}; - const GridEntry *cursorEntry; + const GridEntry* cursorEntry; // Spread out on search axis+ while (mCursor.x < mGridSize.x && mCursor.y < mGridSize.y && mCursor.x >= 0 && @@ -367,7 +368,8 @@ bool ComponentGrid::moveCursor(glm::ivec2 dir) { return false; } -void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell) { +void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell) +{ const glm::ivec2 origCursor{mCursor}; if (xPos != -1) @@ -375,7 +377,7 @@ void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell) { if (yPos != -1) mCursor.y = yPos; - const GridEntry *currentCursorEntry = getCellAt(mCursor); + const GridEntry* currentCursorEntry = getCellAt(mCursor); // If requested, select the leftmost cell of entries wider than 1 cell. if (selectLeftCell && mCursor.x > currentCursorEntry->pos.x) @@ -384,14 +386,16 @@ void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell) { onCursorMoved(origCursor, mCursor); } -void ComponentGrid::onFocusLost() { - const GridEntry *cursorEntry = getCellAt(mCursor); +void ComponentGrid::onFocusLost() +{ + const GridEntry* cursorEntry = getCellAt(mCursor); if (cursorEntry) cursorEntry->component->onFocusLost(); } -void ComponentGrid::onFocusGained() { - const GridEntry *cursorEntry = getCellAt(mCursor); +void ComponentGrid::onFocusGained() +{ + const GridEntry* cursorEntry = getCellAt(mCursor); if (cursorEntry) cursorEntry->component->onFocusGained(); } diff --git a/es-core/src/components/ComponentGrid.h b/es-core/src/components/ComponentGrid.h index 59f1ca852..94300ae68 100644 --- a/es-core/src/components/ComponentGrid.h +++ b/es-core/src/components/ComponentGrid.h @@ -30,33 +30,35 @@ namespace GridFlags } // namespace GridFlags // Provides basic layout of components in an X*Y grid. -class ComponentGrid : public GuiComponent { +class ComponentGrid : public GuiComponent +{ public: - ComponentGrid(Window *window, const glm::ivec2 &gridDimensions); + ComponentGrid(Window* window, const glm::ivec2& gridDimensions); virtual ~ComponentGrid(); - bool removeEntry(const std::shared_ptr &comp); + bool removeEntry(const std::shared_ptr& comp); - void setEntry(const std::shared_ptr &comp, - const glm::ivec2 &pos, + void setEntry(const std::shared_ptr& comp, + const glm::ivec2& pos, bool canFocus, bool resize = true, - const glm::ivec2 &size = glm::ivec2{1, 1}, + const glm::ivec2& size = glm::ivec2{1, 1}, unsigned int border = GridFlags::BORDER_NONE, GridFlags::UpdateType updateType = GridFlags::UPDATE_ALWAYS); - void setPastBoundaryCallback(const std::function &func) { + void setPastBoundaryCallback(const std::function& func) + { mPastBoundaryCallback = func; } - void textInput(const std::string &text) override; + void textInput(const std::string& text) override; - bool input(InputConfig *config, Input input) override; + bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; - void render(const glm::mat4 &parentTrans) override; + void render(const glm::mat4& parentTrans) override; void onSizeChanged() override; @@ -80,10 +82,11 @@ public: // Pass -1 for xPos or yPos to keep its axis cursor position. void moveCursorTo(int xPos, int yPos, bool selectLeftCell = false); - void setCursorTo(const std::shared_ptr &comp); + void setCursorTo(const std::shared_ptr& comp); - std::shared_ptr getSelectedComponent() { - const GridEntry *e = getCellAt(mCursor); + std::shared_ptr getSelectedComponent() + { + const GridEntry* e = getCellAt(mCursor); if (e) return e->component; else @@ -129,25 +132,25 @@ private: }; // Update position and size. - void updateCellComponent(const GridEntry &cell); + void updateCellComponent(const GridEntry& cell); void updateSeparators(); void onCursorMoved(glm::ivec2 from, glm::ivec2 to); - const GridEntry *getCellAt(int x, int y) const; + const GridEntry* getCellAt(int x, int y) const; - const GridEntry *getCellAt(const glm::ivec2 &pos) const { return getCellAt(pos.x, pos.y); } + const GridEntry* getCellAt(const glm::ivec2& pos) const { return getCellAt(pos.x, pos.y); } std::vector> mSeparators; glm::ivec2 mGridSize; std::vector mCells; glm::ivec2 mCursor; - std::function mPastBoundaryCallback; + std::function mPastBoundaryCallback; - float *mRowHeights; - float *mColWidths; + float* mRowHeights; + float* mColWidths; }; #endif // ES_CORE_COMPONENTS_COMPONENT_GRID_H diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index 230e66500..db6e65fff 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -71,10 +71,11 @@ bool ComponentList::input(InputConfig* config, Input input) if (mEntries.at(mCursor).data.input_handler) { if (mEntries.at(mCursor).data.input_handler(config, input)) return true; - } else { + } + else { // No input handler assigned, do the default, which is to give it // to the rightmost element in the row. - auto &row = mEntries.at(mCursor).data; + auto& row = mEntries.at(mCursor).data; if (row.elements.size()) { if (row.elements.back().component->input(config, input)) return true; @@ -196,7 +197,7 @@ void ComponentList::render(const glm::mat4& parentTrans) std::vector drawAfterCursor; bool drawAll; for (size_t i = 0; i < mEntries.size(); i++) { - auto &entry = mEntries.at(i); + auto& entry = mEntries.at(i); drawAll = !mFocused || i != static_cast(mCursor); for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); it++) { if (drawAll || it->invert_when_selected) { diff --git a/es-core/src/components/DateTimeEditComponent.cpp b/es-core/src/components/DateTimeEditComponent.cpp index b1d6b3664..23b900aae 100644 --- a/es-core/src/components/DateTimeEditComponent.cpp +++ b/es-core/src/components/DateTimeEditComponent.cpp @@ -12,10 +12,17 @@ #include "resources/Font.h" #include "utils/StringUtil.h" -DateTimeEditComponent::DateTimeEditComponent(Window *window, bool alignRight, DisplayMode dispMode) - : GuiComponent(window), mEditing(false), mEditIndex(0), mDisplayMode(dispMode), mRelativeUpdateAccumulator(0), - mColor(0x777777FF), mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)), mAlignRight(alignRight), - mUppercase(false), mAutoSize(true) +DateTimeEditComponent::DateTimeEditComponent(Window* window, bool alignRight, DisplayMode dispMode) + : GuiComponent(window) + , mEditing(false) + , mEditIndex(0) + , mDisplayMode(dispMode) + , mRelativeUpdateAccumulator(0) + , mColor(0x777777FF) + , mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)) + , mAlignRight(alignRight) + , mUppercase(false) + , mAutoSize(true) { updateTextCache(); } diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 6665b2883..9213ae095 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -60,9 +60,7 @@ void FlexboxComponent::setItemWidth(float value) } float FlexboxComponent::getItemWidth() { return mItemWidth; } -void FlexboxComponent::onSizeChanged() { - mLayoutValid = false; -} +void FlexboxComponent::onSizeChanged() { mLayoutValid = false; } void FlexboxComponent::computeLayout() { @@ -95,9 +93,9 @@ void FlexboxComponent::computeLayout() // Pre-compute layout parameters. int n = mChildren.size(); - int nLines = std::max(1, (int) std::ceil(n / std::max(1, (int) mItemsPerLine))); + int nLines = std::max(1, (int)std::ceil(n / std::max(1, (int)mItemsPerLine))); float lineWidth = - (mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); + (mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); float anchorXStart = anchorX; float anchorYStart = anchorY; @@ -106,14 +104,15 @@ void FlexboxComponent::computeLayout() if (mDirection == "row") { totalSize.x += (mItemMargin.x + mItemWidth) * mItemsPerLine; totalSize.y += (mItemMargin.y + maxItemSize.y) * nLines; - } else { + } + else { totalSize.x += (mItemMargin.x + mItemWidth) * nLines; totalSize.y += (mItemMargin.y + maxItemSize.y) * mItemsPerLine; } // Iterate through the children. for (int i = 0; i < n; i++) { - GuiComponent *child = mChildren[i]; + GuiComponent* child = mChildren[i]; auto size = child->getSize(); // Top-left anchor position. @@ -132,10 +131,12 @@ void FlexboxComponent::computeLayout() if (mAlign == ITEM_ALIGN_END) { x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) : 0; - } else if (mAlign == ITEM_ALIGN_CENTER) { + } + else if (mAlign == ITEM_ALIGN_CENTER) { x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; - } else if (mAlign == ITEM_ALIGN_STRETCH && mDirection == "row") { + } + else if (mAlign == ITEM_ALIGN_STRETCH && mDirection == "row") { child->setSize(child->getSize().x, maxItemSize.y); } @@ -149,7 +150,7 @@ void FlexboxComponent::computeLayout() child->setPosition(getPosition().x + x, getPosition().y + y); // Translate anchor. - if ((i + 1) % std::max(1, (int) mItemsPerLine) != 0) { + if ((i + 1) % std::max(1, (int)mItemsPerLine) != 0) { // Translate on same line. anchorX += (size.x + mItemMargin.x) * directionLine.x; anchorY += (size.y + mItemMargin.y) * directionLine.y; @@ -159,7 +160,8 @@ void FlexboxComponent::computeLayout() if (directionRow.x == 0) { anchorY += lineWidth * directionRow.y; anchorX = anchorXStart; - } else { + } + else { anchorX += lineWidth * directionRow.x; anchorY = anchorYStart; } @@ -169,7 +171,8 @@ void FlexboxComponent::computeLayout() mLayoutValid = true; } -void FlexboxComponent::render(const glm::mat4& parentTrans) { +void FlexboxComponent::render(const glm::mat4& parentTrans) +{ if (!isVisible()) return; diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index 3729f5a32..69c1ae32c 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -37,36 +37,36 @@ void HelpComponent::assignIcons() ":/help/dpad_updown.svg" : mStyle.mCustomButtons.dpad_updown; sIconPathMap["left/right"] = mStyle.mCustomButtons.dpad_leftright.empty() ? - ":/help/dpad_leftright.svg" : - mStyle.mCustomButtons.dpad_leftright; + ":/help/dpad_leftright.svg" : + mStyle.mCustomButtons.dpad_leftright; sIconPathMap["up/down/left/right"] = mStyle.mCustomButtons.dpad_all.empty() ? - ":/help/dpad_all.svg" : - mStyle.mCustomButtons.dpad_all; + ":/help/dpad_all.svg" : + mStyle.mCustomButtons.dpad_all; sIconPathMap["thumbstickclick"] = mStyle.mCustomButtons.thumbstick_click.empty() ? - ":/help/thumbstick_click.svg" : - mStyle.mCustomButtons.thumbstick_click; + ":/help/thumbstick_click.svg" : + mStyle.mCustomButtons.thumbstick_click; sIconPathMap["l"] = mStyle.mCustomButtons.button_l.empty() ? ":/help/button_l.svg" : - mStyle.mCustomButtons.button_l; + mStyle.mCustomButtons.button_l; sIconPathMap["r"] = mStyle.mCustomButtons.button_r.empty() ? ":/help/button_r.svg" : - mStyle.mCustomButtons.button_r; + mStyle.mCustomButtons.button_r; sIconPathMap["lr"] = mStyle.mCustomButtons.button_lr.empty() ? ":/help/button_lr.svg" : - mStyle.mCustomButtons.button_lr; + mStyle.mCustomButtons.button_lr; sIconPathMap["lt"] = mStyle.mCustomButtons.button_lt.empty() ? ":/help/button_lt.svg" : - mStyle.mCustomButtons.button_lt; + mStyle.mCustomButtons.button_lt; sIconPathMap["rt"] = mStyle.mCustomButtons.button_rt.empty() ? ":/help/button_rt.svg" : - mStyle.mCustomButtons.button_rt; + mStyle.mCustomButtons.button_rt; // These graphics files are custom per controller type. if (controllerType == "snes") { sIconPathMap["a"] = mStyle.mCustomButtons.button_a_SNES.empty() ? - ":/help/button_a_SNES.svg" : - mStyle.mCustomButtons.button_a_SNES; + ":/help/button_a_SNES.svg" : + mStyle.mCustomButtons.button_a_SNES; sIconPathMap["b"] = mStyle.mCustomButtons.button_b_SNES.empty() ? - ":/help/button_b_SNES.svg" : - mStyle.mCustomButtons.button_b_SNES; + ":/help/button_b_SNES.svg" : + mStyle.mCustomButtons.button_b_SNES; sIconPathMap["x"] = mStyle.mCustomButtons.button_x_SNES.empty() ? - ":/help/button_x_SNES.svg" : - mStyle.mCustomButtons.button_x_SNES; + ":/help/button_x_SNES.svg" : + mStyle.mCustomButtons.button_x_SNES; sIconPathMap["y"] = mStyle.mCustomButtons.button_y_SNES.empty() ? ":/help/button_y_SNES.svg" : mStyle.mCustomButtons.button_y_SNES; diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 0787d53d5..b7c982530 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -27,11 +27,23 @@ glm::vec2 ImageComponent::getSize() const return GuiComponent::getSize() * (mBottomRightCrop - mTopLeftCrop); } -ImageComponent::ImageComponent(Window *window, bool forceLoad, bool dynamic) - : GuiComponent(window), mTargetSize({}), mFlipX(false), mFlipY(false), mTargetIsMax(false), mTargetIsMin(false), - mColorShift(0xFFFFFFFF), mColorShiftEnd(0xFFFFFFFF), mColorGradientHorizontal(true), mFadeOpacity(0), - mFading(false), mForceLoad(forceLoad), mDynamic(dynamic), mRotateByTargetSize(false), mTopLeftCrop({}), - mBottomRightCrop(1.0f, 1.0f) +ImageComponent::ImageComponent(Window* window, bool forceLoad, bool dynamic) + : GuiComponent(window) + , mTargetSize({}) + , mFlipX(false) + , mFlipY(false) + , mTargetIsMax(false) + , mTargetIsMin(false) + , mColorShift(0xFFFFFFFF) + , mColorShiftEnd(0xFFFFFFFF) + , mColorGradientHorizontal(true) + , mFadeOpacity(0) + , mFading(false) + , mForceLoad(forceLoad) + , mDynamic(dynamic) + , mRotateByTargetSize(false) + , mTopLeftCrop({}) + , mBottomRightCrop(1.0f, 1.0f) { updateColors(); } @@ -126,7 +138,8 @@ void ImageComponent::resize() onSizeChanged(); } -void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify, bool cacheImage) { +void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify, bool cacheImage) +{ // Always load bundled graphic resources statically, unless mForceLoad has been set. // This eliminates annoying texture pop-in problems that would otherwise occur. if (!mForceLoad && (path[0] == ':') && (path[1] == '/')) { @@ -137,11 +150,12 @@ void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify, b if (mDefaultPath.empty() || !ResourceManager::getInstance()->fileExists(mDefaultPath)) mTexture.reset(); else - mTexture = - TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, linearMagnify, 1.0f, cacheImage); + mTexture = TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, linearMagnify, + 1.0f, cacheImage); } else { - mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, linearMagnify, 1.0f, cacheImage); + mTexture = + TextureResource::get(path, tile, mForceLoad, mDynamic, linearMagnify, 1.0f, cacheImage); } resize(); diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index c0bb05c91..474f46a1e 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -14,9 +14,10 @@ class TextureResource; -class ImageComponent : public GuiComponent { +class ImageComponent : public GuiComponent +{ public: - ImageComponent(Window *window, bool forceLoad = false, bool dynamic = true); + ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true); virtual ~ImageComponent() noexcept {}; @@ -24,13 +25,16 @@ public: // Loads the image at the given filepath. Will tile if tile is true (retrieves texture // as tiling, creates vertices accordingly). - void setImage(std::string path, bool tile = false, bool linearMagnify = false, bool cacheSVG = false); + void setImage(std::string path, + bool tile = false, + bool linearMagnify = false, + bool cacheSVG = false); // Loads an image from memory. - void setImage(const char *data, size_t length, bool tile = false); + void setImage(const char* data, size_t length, bool tile = false); // Use an already existing texture. - void setImage(const std::shared_ptr &texture); + void setImage(const std::shared_ptr& texture); void onSizeChanged() override { updateVertices(); } diff --git a/es-core/src/guis/GuiInputConfig.cpp b/es-core/src/guis/GuiInputConfig.cpp index aa9767459..516d17c6e 100644 --- a/es-core/src/guis/GuiInputConfig.cpp +++ b/es-core/src/guis/GuiInputConfig.cpp @@ -181,7 +181,7 @@ GuiInputConfig::GuiInputConfig(Window* window, }; buttons.push_back( - std::make_shared(mWindow, "OK", "ok", [okFunction] { okFunction(); })); + std::make_shared(mWindow, "OK", "ok", [okFunction] { okFunction(); })); mButtonGrid = makeButtonGrid(mWindow, buttons); mGrid.setEntry(mButtonGrid, glm::ivec2{0, 6}, true, false); diff --git a/es-core/src/guis/GuiMsgBox.cpp b/es-core/src/guis/GuiMsgBox.cpp index 5e648eb26..0e0a7fcb2 100644 --- a/es-core/src/guis/GuiMsgBox.cpp +++ b/es-core/src/guis/GuiMsgBox.cpp @@ -15,18 +15,22 @@ #define HORIZONTAL_PADDING_PX 20.0f GuiMsgBox::GuiMsgBox(Window* window, - const HelpStyle &helpstyle, - const std::string &text, - const std::string &name1, - const std::function &func1, - const std::string &name2, - const std::function &func2, - const std::string &name3, - const std::function &func3, + const HelpStyle& helpstyle, + const std::string& text, + const std::string& name1, + const std::function& func1, + const std::string& name2, + const std::function& func2, + const std::string& name3, + const std::function& func3, bool disableBackButton, bool deleteOnButtonPress) - : GuiComponent(window), mBackground(window, ":/graphics/frame.svg"), mGrid(window, glm::ivec2{1, 2}), - mHelpStyle(helpstyle), mDisableBackButton(disableBackButton), mDeleteOnButtonPress(deleteOnButtonPress) + : GuiComponent(window) + , mBackground(window, ":/graphics/frame.svg") + , mGrid(window, glm::ivec2{1, 2}) + , mHelpStyle(helpstyle) + , mDisableBackButton(disableBackButton) + , mDeleteOnButtonPress(deleteOnButtonPress) { // Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent // regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference. diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp index 4edd8d95b..504fc942c 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp @@ -74,33 +74,47 @@ std::vector> kbLastRowLoad{ // clang-format on GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( - Window *window, - const HelpStyle &helpstyle, - const std::string &title, - const std::string &initValue, - const std::function &okCallback, - bool multiLine, - const std::string &acceptBtnHelpText, - const std::string &saveConfirmationText, - const std::string &infoString, - const std::string &defaultValue, - const std::string &loadBtnHelpText, - const std::string &clearBtnHelpText, - const std::string &cancelBtnHelpText) - : GuiComponent{window}, mBackground{window, ":/graphics/frame.svg"}, - mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 8 : 6)}}, mHelpStyle{helpstyle}, - mInitValue{initValue}, mAcceptBtnHelpText{acceptBtnHelpText}, mSaveConfirmationText{saveConfirmationText}, - mLoadBtnHelpText{loadBtnHelpText}, mClearBtnHelpText{clearBtnHelpText}, mCancelBtnHelpText{cancelBtnHelpText}, - mOkCallback{okCallback}, mMultiLine{multiLine}, mComplexMode{(infoString != "" && defaultValue != "")}, - mDeleteRepeat{false}, mShift{false}, mAlt{false}, mDeleteRepeatTimer{0}, mNavigationRepeatTimer{0}, - mNavigationRepeatDirX{0}, mNavigationRepeatDirY{0} { + Window* window, + const HelpStyle& helpstyle, + const std::string& title, + const std::string& initValue, + const std::function& okCallback, + bool multiLine, + const std::string& acceptBtnHelpText, + const std::string& saveConfirmationText, + const std::string& infoString, + const std::string& defaultValue, + const std::string& loadBtnHelpText, + const std::string& clearBtnHelpText, + const std::string& cancelBtnHelpText) + : GuiComponent{window} + , mBackground{window, ":/graphics/frame.svg"} + , mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 8 : 6)}} + , mHelpStyle{helpstyle} + , mInitValue{initValue} + , mAcceptBtnHelpText{acceptBtnHelpText} + , mSaveConfirmationText{saveConfirmationText} + , mLoadBtnHelpText{loadBtnHelpText} + , mClearBtnHelpText{clearBtnHelpText} + , mCancelBtnHelpText{cancelBtnHelpText} + , mOkCallback{okCallback} + , mMultiLine{multiLine} + , mComplexMode{(infoString != "" && defaultValue != "")} + , mDeleteRepeat{false} + , mShift{false} + , mAlt{false} + , mDeleteRepeatTimer{0} + , mNavigationRepeatTimer{0} + , mNavigationRepeatDirX{0} + , mNavigationRepeatDirY{0} +{ addChild(&mBackground); addChild(&mGrid); mTitle = std::make_shared(mWindow, Utils::String::toUpper(title), Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); - std::vector> kbLayout; + std::vector> kbLayout; // At the moment there is only the US keyboard layout available. kbLayout.insert(kbLayout.cend(), kbBaseUS.cbegin(), kbBaseUS.cend()); @@ -114,7 +128,7 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( mHorizontalKeyCount = static_cast(kbLayout[0].size()); mKeyboardGrid = std::make_shared( - mWindow, glm::ivec2(mHorizontalKeyCount, static_cast(kbLayout.size()) / 3)); + mWindow, glm::ivec2(mHorizontalKeyCount, static_cast(kbLayout.size()) / 3)); mText = std::make_shared(mWindow); mText->setValue(initValue); @@ -129,11 +143,11 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( if (mComplexMode) { mInfoString = std::make_shared( - mWindow, infoString, Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER); + mWindow, infoString, Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER); mGrid.setEntry(mInfoString, glm::ivec2{0, yPos}, false, true); mDefaultValue = std::make_shared( - mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); + mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); mGrid.setEntry(mDefaultValue, glm::ivec2{0, yPos + 1}, false, true); yPos += 2; } @@ -164,17 +178,20 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( upper = DELETE_SYMBOL; alted = DELETE_SYMBOL; altshifted = DELETE_SYMBOL; - } else if (lower == "OK") { + } + else if (lower == "OK") { lower = OK_SYMBOL; upper = OK_SYMBOL; alted = OK_SYMBOL; altshifted = OK_SYMBOL; - } else if (lower == "SPACE") { + } + else if (lower == "SPACE") { lower = " "; upper = " "; alted = " "; altshifted = " "; - } else if (lower != "SHIFT" && lower.length() > 1) { + } + else if (lower != "SHIFT" && lower.length() > 1) { lower = (lower.c_str()); upper = (upper.c_str()); alted = (alted.c_str()); @@ -183,19 +200,21 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( if (lower == "SHIFT") { mShiftButton = std::make_shared( - mWindow, (SHIFT_SYMBOL), ("SHIFT"), [this] { shiftKeys(); }, false, true); + mWindow, (SHIFT_SYMBOL), ("SHIFT"), [this] { shiftKeys(); }, false, true); button = mShiftButton; - } else if (lower == "ALT") { + } + else if (lower == "ALT") { mAltButton = std::make_shared( - mWindow, (ALT_SYMBOL), ("ALT"), [this] { altKeys(); }, false, true); + mWindow, (ALT_SYMBOL), ("ALT"), [this] { altKeys(); }, false, true); button = mAltButton; - } else { + } + else { button = makeButton(lower, upper, alted, altshifted); } button->setPadding( - glm::vec4(BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f, - BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f)); + glm::vec4(BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f, + BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f)); buttons.push_back(button); int colSpan = 1; @@ -233,13 +252,14 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( mText->setSize(0.0f, textHeight); // If attempting to navigate beyond the edge of the keyboard grid, then wrap around. - mGrid.setPastBoundaryCallback([this, kbLayout](InputConfig *config, Input input) -> bool { + mGrid.setPastBoundaryCallback([this, kbLayout](InputConfig* config, Input input) -> bool { if (config->isMappedLike("left", input)) { if (mGrid.getSelectedComponent() == mKeyboardGrid) { mKeyboardGrid->moveCursorTo(mHorizontalKeyCount - 1, -1, true); return true; } - } else if (config->isMappedLike("right", input)) { + } + else if (config->isMappedLike("right", input)) { if (mGrid.getSelectedComponent() == mKeyboardGrid) { mKeyboardGrid->moveCursorTo(0, -1); return true; @@ -259,7 +279,8 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( setPosition((static_cast(Renderer::getScreenWidth()) - mSize.x) / 2.0f, (static_cast(Renderer::getScreenHeight()) - mSize.y) / 2.0f); - } else { + } + else { if (mComplexMode) setSize(width, KEYBOARD_HEIGHT + mDefaultValue->getSize().y * 3.0f); else @@ -270,7 +291,8 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( } } -void GuiTextEditKeyboardPopup::onSizeChanged() { +void GuiTextEditKeyboardPopup::onSizeChanged() +{ mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); mText->setSize(mSize.x - KEYBOARD_PADDINGX - KEYBOARD_PADDINGX, mText->getSize().y); @@ -281,7 +303,8 @@ void GuiTextEditKeyboardPopup::onSizeChanged() { mGrid.setRowHeightPerc(1, (mInfoString->getSize().y * 0.6f) / mSize.y); mGrid.setRowHeightPerc(2, (mDefaultValue->getSize().y * 1.6f) / mSize.y); mGrid.setRowHeightPerc(1, (mText->getSize().y * 1.0f) / mSize.y); - } else if (mMultiLine) { + } + else if (mMultiLine) { mGrid.setRowHeightPerc(1, (mText->getSize().y * 1.15f) / mSize.y); } @@ -296,7 +319,8 @@ void GuiTextEditKeyboardPopup::onSizeChanged() { mKeyboardGrid->setPosition(KEYBOARD_PADDINGX, pos.y); } -bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { +bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input) +{ // Enter/return key or numpad enter key accepts the changes. if (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && !mMultiLine && input.value && (input.id == SDLK_RETURN || input.id == SDLK_KP_ENTER)) { @@ -304,7 +328,7 @@ bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { delete this; return true; } - // Dito for the A button if using a controller. + // Dito for the A button if using a controller. else if (config->getDeviceId() != DEVICE_KEYBOARD && mText->isEditing() && config->isMappedTo("a", input) && input.value) { this->mOkCallback(mText->getValue()); @@ -330,18 +354,19 @@ bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { if (mText->getValue() != mInitValue) { // Changes were made, ask if the user wants to save them. mWindow->pushGui(new GuiMsgBox( - mWindow, mHelpStyle, mSaveConfirmationText, "YES", - [this] { - this->mOkCallback(mText->getValue()); - delete this; - return true; - }, - "NO", - [this] { - delete this; - return true; - })); - } else { + mWindow, mHelpStyle, mSaveConfirmationText, "YES", + [this] { + this->mOkCallback(mText->getValue()); + delete this; + return true; + }, + "NO", + [this] { + delete this; + return true; + })); + } + else { delete this; return true; } @@ -374,7 +399,8 @@ bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { if (!editing) mText->stopEditing(); - } else { + } + else { mDeleteRepeat = false; } return true; @@ -400,7 +426,8 @@ bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { if (input.value) { mNavigationRepeatDirX = -1; mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); - } else { + } + else { mNavigationRepeatDirX = 0; } } @@ -409,7 +436,8 @@ bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { if (input.value) { mNavigationRepeatDirX = 1; mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); - } else { + } + else { mNavigationRepeatDirX = 0; } } @@ -418,7 +446,8 @@ bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { if (input.value) { mNavigationRepeatDirY = -1; mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); - } else { + } + else { mNavigationRepeatDirY = 0; } } @@ -427,7 +456,8 @@ bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { if (input.value) { mNavigationRepeatDirY = 1; mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED); - } else { + } + else { mNavigationRepeatDirY = 0; } } @@ -438,19 +468,22 @@ bool GuiTextEditKeyboardPopup::input(InputConfig *config, Input input) { return false; } -void GuiTextEditKeyboardPopup::update(int deltaTime) { +void GuiTextEditKeyboardPopup::update(int deltaTime) +{ updateNavigationRepeat(deltaTime); updateDeleteRepeat(deltaTime); GuiComponent::update(deltaTime); } -std::vector GuiTextEditKeyboardPopup::getHelpPrompts() { +std::vector GuiTextEditKeyboardPopup::getHelpPrompts() +{ std::vector prompts = mGrid.getHelpPrompts(); if (!mText->isEditing()) { prompts.push_back(HelpPrompt("lt", "shift")); prompts.push_back(HelpPrompt("rt", "alt")); - } else { + } + else { prompts.push_back(HelpPrompt("a", mAcceptBtnHelpText)); } @@ -480,7 +513,8 @@ std::vector GuiTextEditKeyboardPopup::getHelpPrompts() { return prompts; } -void GuiTextEditKeyboardPopup::updateDeleteRepeat(int deltaTime) { +void GuiTextEditKeyboardPopup::updateDeleteRepeat(int deltaTime) +{ if (!mDeleteRepeat) return; @@ -500,7 +534,8 @@ void GuiTextEditKeyboardPopup::updateDeleteRepeat(int deltaTime) { } } -void GuiTextEditKeyboardPopup::updateNavigationRepeat(int deltaTime) { +void GuiTextEditKeyboardPopup::updateNavigationRepeat(int deltaTime) +{ if (mNavigationRepeatDirX == 0 && mNavigationRepeatDirY == 0) return; @@ -527,13 +562,15 @@ void GuiTextEditKeyboardPopup::updateNavigationRepeat(int deltaTime) { } } -void GuiTextEditKeyboardPopup::shiftKeys() { +void GuiTextEditKeyboardPopup::shiftKeys() +{ mShift = !mShift; if (mShift) { mShiftButton->setFlatColorFocused(0xFF2222FF); mShiftButton->setFlatColorUnfocused(0xFF2222FF); - } else { + } + else { mShiftButton->setFlatColorFocused(0x878787FF); mShiftButton->setFlatColorUnfocused(0x60606025); } @@ -547,9 +584,10 @@ void GuiTextEditKeyboardPopup::shiftKeys() { if (mAlt) { altKeys(); altKeys(); - } else { - for (auto &kb: mKeyboardButtons) { - const std::string &text = mShift ? kb.shiftedKey : kb.key; + } + else { + for (auto& kb : mKeyboardButtons) { + const std::string& text = mShift ? kb.shiftedKey : kb.key; auto sz = kb.button->getSize(); kb.button->setText(text, text, false); kb.button->setSize(sz); @@ -557,13 +595,15 @@ void GuiTextEditKeyboardPopup::shiftKeys() { } } -void GuiTextEditKeyboardPopup::altKeys() { +void GuiTextEditKeyboardPopup::altKeys() +{ mAlt = !mAlt; if (mAlt) { mAltButton->setFlatColorFocused(0xFF2222FF); mAltButton->setFlatColorUnfocused(0xFF2222FF); - } else { + } + else { mAltButton->setFlatColorFocused(0x878787FF); mAltButton->setFlatColorUnfocused(0x60606025); } @@ -577,9 +617,10 @@ void GuiTextEditKeyboardPopup::altKeys() { if (mShift) { shiftKeys(); shiftKeys(); - } else { - for (auto &kb: mKeyboardButtons) { - const std::string &text = mAlt ? kb.altedKey : kb.key; + } + else { + for (auto& kb : mKeyboardButtons) { + const std::string& text = mAlt ? kb.altedKey : kb.key; auto sz = kb.button->getSize(); kb.button->setText(text, text, false); kb.button->setSize(sz); @@ -587,9 +628,10 @@ void GuiTextEditKeyboardPopup::altKeys() { } } -void GuiTextEditKeyboardPopup::altShiftKeys() { - for (auto &kb: mKeyboardButtons) { - const std::string &text = kb.altshiftedKey; +void GuiTextEditKeyboardPopup::altShiftKeys() +{ + for (auto& kb : mKeyboardButtons) { + const std::string& text = kb.altshiftedKey; auto sz = kb.button->getSize(); kb.button->setText(text, text, false); kb.button->setSize(sz); @@ -597,56 +639,62 @@ void GuiTextEditKeyboardPopup::altShiftKeys() { } std::shared_ptr GuiTextEditKeyboardPopup::makeButton( - const std::string &key, - const std::string &shiftedKey, - const std::string &altedKey, - const std::string &altshiftedKey) { + const std::string& key, + const std::string& shiftedKey, + const std::string& altedKey, + const std::string& altshiftedKey) +{ std::shared_ptr button = std::make_shared( - mWindow, key, key, - [this, key, shiftedKey, altedKey, altshiftedKey] { - if (key == (OK_SYMBOL) || key.find("OK") != std::string::npos) { - mOkCallback(mText->getValue()); - delete this; - return; - } else if (key == (DELETE_SYMBOL) || key == "DEL") { - mText->startEditing(); - mText->textInput("\b"); - mText->stopEditing(); - return; - } else if (key == "SPACE" || key == " ") { - mText->startEditing(); - mText->textInput(" "); - mText->stopEditing(); - return; - } else if (key == "LOAD") { - mText->setValue(mDefaultValue->getValue()); - mText->setCursor(mDefaultValue->getValue().size()); - return; - } else if (key == "CLEAR") { - mText->setValue(""); - return; - } else if (key == "CANCEL") { - delete this; - return; - } - - if (mAlt && altedKey.empty()) - return; - + mWindow, key, key, + [this, key, shiftedKey, altedKey, altshiftedKey] { + if (key == (OK_SYMBOL) || key.find("OK") != std::string::npos) { + mOkCallback(mText->getValue()); + delete this; + return; + } + else if (key == (DELETE_SYMBOL) || key == "DEL") { mText->startEditing(); - - if (mShift && mAlt) - mText->textInput(altshiftedKey.c_str()); - else if (mAlt) - mText->textInput(altedKey.c_str()); - else if (mShift) - mText->textInput(shiftedKey.c_str()); - else - mText->textInput(key.c_str()); - + mText->textInput("\b"); mText->stopEditing(); - }, - false, true); + return; + } + else if (key == "SPACE" || key == " ") { + mText->startEditing(); + mText->textInput(" "); + mText->stopEditing(); + return; + } + else if (key == "LOAD") { + mText->setValue(mDefaultValue->getValue()); + mText->setCursor(mDefaultValue->getValue().size()); + return; + } + else if (key == "CLEAR") { + mText->setValue(""); + return; + } + else if (key == "CANCEL") { + delete this; + return; + } + + if (mAlt && altedKey.empty()) + return; + + mText->startEditing(); + + if (mShift && mAlt) + mText->textInput(altshiftedKey.c_str()); + else if (mAlt) + mText->textInput(altedKey.c_str()); + else if (mShift) + mText->textInput(shiftedKey.c_str()); + else + mText->textInput(key.c_str()); + + mText->stopEditing(); + }, + false, true); KeyboardButton kb(button, key, shiftedKey, altedKey, altshiftedKey); mKeyboardButtons.push_back(kb); diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.h b/es-core/src/guis/GuiTextEditKeyboardPopup.h index 4104cb6cb..6ec0ac8b4 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.h +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.h @@ -15,25 +15,26 @@ #include "components/ComponentGrid.h" #include "components/TextEditComponent.h" -class GuiTextEditKeyboardPopup : public GuiComponent { +class GuiTextEditKeyboardPopup : public GuiComponent +{ public: - GuiTextEditKeyboardPopup(Window *window, - const HelpStyle &helpstyle, - const std::string &title, - const std::string &initValue, - const std::function &okCallback, + GuiTextEditKeyboardPopup(Window* window, + const HelpStyle& helpstyle, + const std::string& title, + const std::string& initValue, + const std::function& okCallback, bool multiLine, - const std::string &acceptBtnHelpText = "OK", - const std::string &saveConfirmationText = "SAVE CHANGES?", - const std::string &infoString = "", - const std::string &defaultValue = "", - const std::string &loadBtnHelpText = "LOAD DEFAULT", - const std::string &clearBtnHelpText = "CLEAR", - const std::string &cancelBtnHelpText = "DISCARD CHANGES"); + const std::string& acceptBtnHelpText = "OK", + const std::string& saveConfirmationText = "SAVE CHANGES?", + const std::string& infoString = "", + const std::string& defaultValue = "", + const std::string& loadBtnHelpText = "LOAD DEFAULT", + const std::string& clearBtnHelpText = "CLEAR", + const std::string& cancelBtnHelpText = "DISCARD CHANGES"); void onSizeChanged() override; - bool input(InputConfig *config, Input input) override; + bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; @@ -42,7 +43,8 @@ public: HelpStyle getHelpStyle() override { return mHelpStyle; } private: - class KeyboardButton { + class KeyboardButton + { public: std::shared_ptr button; const std::string key; @@ -51,11 +53,15 @@ private: const std::string altshiftedKey; KeyboardButton(const std::shared_ptr b, - const std::string &k, - const std::string &sk, - const std::string &ak, - const std::string &ask) - : button{b}, key{k}, shiftedKey{sk}, altedKey{ak}, altshiftedKey{ask} {}; + const std::string& k, + const std::string& sk, + const std::string& ak, + const std::string& ask) + : button{b} + , key{k} + , shiftedKey{sk} + , altedKey{ak} + , altshiftedKey{ask} {}; }; void updateDeleteRepeat(int deltaTime); @@ -68,10 +74,10 @@ private: void altShiftKeys(); - std::shared_ptr makeButton(const std::string &key, - const std::string &shiftedKey, - const std::string &altedKey, - const std::string &altshiftedKey); + std::shared_ptr makeButton(const std::string& key, + const std::string& shiftedKey, + const std::string& altedKey, + const std::string& altshiftedKey); std::vector mKeyboardButtons; @@ -95,7 +101,7 @@ private: std::string mClearBtnHelpText; std::string mCancelBtnHelpText; - std::function mOkCallback; + std::function mOkCallback; bool mMultiLine; bool mComplexMode; diff --git a/es-core/src/guis/GuiTextEditPopup.cpp b/es-core/src/guis/GuiTextEditPopup.cpp index 898f4a4a6..132d81056 100644 --- a/es-core/src/guis/GuiTextEditPopup.cpp +++ b/es-core/src/guis/GuiTextEditPopup.cpp @@ -15,25 +15,35 @@ #include "components/MenuComponent.h" #include "guis/GuiMsgBox.h" -GuiTextEditPopup::GuiTextEditPopup(Window *window, - const HelpStyle &helpstyle, - const std::string &title, - const std::string &initValue, - const std::function &okCallback, +GuiTextEditPopup::GuiTextEditPopup(Window* window, + const HelpStyle& helpstyle, + const std::string& title, + const std::string& initValue, + const std::function& okCallback, bool multiLine, - const std::string &acceptBtnText, - const std::string &saveConfirmationText, - const std::string &infoString, - const std::string &defaultValue, - const std::string &loadBtnHelpText, - const std::string &clearBtnHelpText, - const std::string &cancelBtnHelpText) - : GuiComponent{window}, mBackground{window, ":/graphics/frame.svg"}, - mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 5 : 3)}}, mHelpStyle{helpstyle}, - mInitValue{initValue}, mAcceptBtnText{acceptBtnText}, mSaveConfirmationText{saveConfirmationText}, - mLoadBtnHelpText{loadBtnHelpText}, mClearBtnHelpText{clearBtnHelpText}, mCancelBtnHelpText{cancelBtnHelpText}, - mOkCallback{okCallback}, mMultiLine{multiLine}, mComplexMode{(infoString != "" && defaultValue != "")}, - mDeleteRepeat{false}, mDeleteRepeatTimer{0} { + const std::string& acceptBtnText, + const std::string& saveConfirmationText, + const std::string& infoString, + const std::string& defaultValue, + const std::string& loadBtnHelpText, + const std::string& clearBtnHelpText, + const std::string& cancelBtnHelpText) + : GuiComponent{window} + , mBackground{window, ":/graphics/frame.svg"} + , mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 5 : 3)}} + , mHelpStyle{helpstyle} + , mInitValue{initValue} + , mAcceptBtnText{acceptBtnText} + , mSaveConfirmationText{saveConfirmationText} + , mLoadBtnHelpText{loadBtnHelpText} + , mClearBtnHelpText{clearBtnHelpText} + , mCancelBtnHelpText{cancelBtnHelpText} + , mOkCallback{okCallback} + , mMultiLine{multiLine} + , mComplexMode{(infoString != "" && defaultValue != "")} + , mDeleteRepeat{false} + , mDeleteRepeatTimer{0} +{ addChild(&mBackground); addChild(&mGrid); @@ -42,9 +52,9 @@ GuiTextEditPopup::GuiTextEditPopup(Window *window, if (mComplexMode) { mInfoString = std::make_shared( - mWindow, infoString, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); + mWindow, infoString, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); mDefaultValue = std::make_shared( - mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); + mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER); } mText = std::make_shared(mWindow); @@ -58,11 +68,11 @@ GuiTextEditPopup::GuiTextEditPopup(Window *window, })); if (mComplexMode) { buttons.push_back(std::make_shared( - mWindow, "load", loadBtnHelpText, [this, defaultValue] { - mText->setValue(defaultValue); - mText->setCursor(0); - mText->setCursor(defaultValue.size()); - })); + mWindow, "load", loadBtnHelpText, [this, defaultValue] { + mText->setValue(defaultValue); + mText->setCursor(0); + mText->setCursor(defaultValue.size()); + })); } buttons.push_back(std::make_shared(mWindow, "clear", clearBtnHelpText, @@ -99,21 +109,22 @@ GuiTextEditPopup::GuiTextEditPopup(Window *window, if (mComplexMode) { float infoWidth = - glm::clamp(0.70f * aspectValue, 0.34f, 0.85f) * Renderer::getScreenWidth(); + glm::clamp(0.70f * aspectValue, 0.34f, 0.85f) * Renderer::getScreenWidth(); float windowWidth = - glm::clamp(0.75f * aspectValue, 0.40f, 0.90f) * Renderer::getScreenWidth(); + glm::clamp(0.75f * aspectValue, 0.40f, 0.90f) * Renderer::getScreenWidth(); mDefaultValue->setSize(infoWidth, mDefaultValue->getFont()->getHeight()); setSize(windowWidth, mTitle->getFont()->getHeight() + textHeight + - mButtonGrid->getSize().y + mButtonGrid->getSize().y * 1.85f); + mButtonGrid->getSize().y + mButtonGrid->getSize().y * 1.85f); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); - } else { + } + else { float width = glm::clamp(0.54f * aspectValue, 0.20f, 0.70f) * Renderer::getScreenWidth(); setSize(width, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y + - mButtonGrid->getSize().y / 2.0f); + mButtonGrid->getSize().y / 2.0f); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); } @@ -124,7 +135,8 @@ GuiTextEditPopup::GuiTextEditPopup(Window *window, mText->startEditing(); } -void GuiTextEditPopup::onSizeChanged() { +void GuiTextEditPopup::onSizeChanged() +{ mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); mText->setSize(mSize.x - 40.0f * Renderer::getScreenHeightModifier(), mText->getSize().y); @@ -138,7 +150,8 @@ void GuiTextEditPopup::onSizeChanged() { mGrid.setSize(mSize); } -bool GuiTextEditPopup::input(InputConfig *config, Input input) { +bool GuiTextEditPopup::input(InputConfig* config, Input input) +{ // Enter key (main key or via numpad) accepts the changes. if (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && !mMultiLine && input.value && (input.id == SDLK_RETURN || input.id == SDLK_KP_ENTER)) { @@ -146,7 +159,7 @@ bool GuiTextEditPopup::input(InputConfig *config, Input input) { delete this; return true; } - // Dito for the A button if using a controller. + // Dito for the A button if using a controller. else if (config->getDeviceId() != DEVICE_KEYBOARD && mText->isEditing() && config->isMappedTo("a", input) && input.value) { this->mOkCallback(mText->getValue()); @@ -166,18 +179,19 @@ bool GuiTextEditPopup::input(InputConfig *config, Input input) { if (mText->getValue() != mInitValue) { // Changes were made, ask if the user wants to save them. mWindow->pushGui(new GuiMsgBox( - mWindow, mHelpStyle, mSaveConfirmationText, "YES", - [this] { - this->mOkCallback(mText->getValue()); - delete this; - return true; - }, - "NO", - [this] { - delete this; - return true; - })); - } else { + mWindow, mHelpStyle, mSaveConfirmationText, "YES", + [this] { + this->mOkCallback(mText->getValue()); + delete this; + return true; + }, + "NO", + [this] { + delete this; + return true; + })); + } + else { delete this; return true; } @@ -202,7 +216,8 @@ bool GuiTextEditPopup::input(InputConfig *config, Input input) { if (!editing) mText->stopEditing(); - } else { + } + else { mDeleteRepeat = false; } return true; @@ -228,12 +243,14 @@ bool GuiTextEditPopup::input(InputConfig *config, Input input) { return false; } -void GuiTextEditPopup::update(int deltaTime) { +void GuiTextEditPopup::update(int deltaTime) +{ updateDeleteRepeat(deltaTime); GuiComponent::update(deltaTime); } -std::vector GuiTextEditPopup::getHelpPrompts() { +std::vector GuiTextEditPopup::getHelpPrompts() +{ std::vector prompts = mGrid.getHelpPrompts(); if (mText->isEditing()) @@ -245,7 +262,8 @@ std::vector GuiTextEditPopup::getHelpPrompts() { return prompts; } -void GuiTextEditPopup::updateDeleteRepeat(int deltaTime) { +void GuiTextEditPopup::updateDeleteRepeat(int deltaTime) +{ if (!mDeleteRepeat) return; diff --git a/es-core/src/guis/GuiTextEditPopup.h b/es-core/src/guis/GuiTextEditPopup.h index 4784cecf7..70c473f2c 100644 --- a/es-core/src/guis/GuiTextEditPopup.h +++ b/es-core/src/guis/GuiTextEditPopup.h @@ -15,25 +15,26 @@ #include "components/ComponentGrid.h" #include "components/TextEditComponent.h" -class GuiTextEditPopup : public GuiComponent { +class GuiTextEditPopup : public GuiComponent +{ public: - GuiTextEditPopup(Window *window, - const HelpStyle &helpstyle, - const std::string &title, - const std::string &initValue, - const std::function &okCallback, + GuiTextEditPopup(Window* window, + const HelpStyle& helpstyle, + const std::string& title, + const std::string& initValue, + const std::function& okCallback, bool multiLine, - const std::string &acceptBtnText = "OK", - const std::string &saveConfirmationText = "SAVE CHANGES?", - const std::string &infoString = "", - const std::string &defaultValue = "", - const std::string &loadBtnHelpText = "LOAD DEFAULT", - const std::string &clearBtnHelpText = "CLEAR", - const std::string &cancelBtnHelpText = "DISCARD CHANGES"); + const std::string& acceptBtnText = "OK", + const std::string& saveConfirmationText = "SAVE CHANGES?", + const std::string& infoString = "", + const std::string& defaultValue = "", + const std::string& loadBtnHelpText = "LOAD DEFAULT", + const std::string& clearBtnHelpText = "CLEAR", + const std::string& cancelBtnHelpText = "DISCARD CHANGES"); void onSizeChanged() override; - bool input(InputConfig *config, Input input) override; + bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; @@ -61,7 +62,7 @@ private: std::string mClearBtnHelpText; std::string mCancelBtnHelpText; - std::function mOkCallback; + std::function mOkCallback; bool mMultiLine; bool mComplexMode; diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 21c15346f..5cdb52e53 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -562,18 +562,18 @@ float Font::getNewlineStartOffset(const std::string& text, endChar = static_cast(text.find('\n', charStart)); return (xLen - sizeText(text.substr(charStart, static_cast(endChar) != std::string::npos ? - endChar - charStart : - endChar)) - .x) / + endChar - charStart : + endChar)) + .x) / 2.0f; } case ALIGN_RIGHT: { int endChar = static_cast(text.find('\n', charStart)); return xLen - (sizeText(text.substr(charStart, static_cast(endChar) != std::string::npos ? - endChar - charStart : - endChar)) - .x); + endChar - charStart : + endChar)) + .x); } default: return 0; diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index 317a8c99d..b1444ce21 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -143,7 +143,7 @@ bool TextureResource::bind() } } -std::shared_ptr TextureResource::get(const std::string &path, +std::shared_ptr TextureResource::get(const std::string& path, bool tile, bool forceLoad, bool dynamic, diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 0f6c42f6f..61abe0f55 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -25,7 +25,7 @@ class TextureData; class TextureResource : public IReloadable { public: - static std::shared_ptr get(const std::string &path, + static std::shared_ptr get(const std::string& path, bool tile = false, bool forceLoad = false, bool dynamic = true, @@ -33,7 +33,7 @@ public: float scaleDuringLoad = 1.0f, bool cacheImage = false); - void initFromPixels(const unsigned char *dataRGBA, size_t width, size_t height); + void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height); virtual void initFromMemory(const char* data, size_t length); static void manualUnload(std::string path, bool tile); From c51ad4a4327cfc01d08beba4b00c27757890e5d8 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 27 Sep 2021 21:16:05 +0200 Subject: [PATCH 026/128] Removed some noexcept operators. --- es-app/src/views/gamelist/VideoGameListView.h | 3 +-- es-core/src/GuiComponent.h | 2 +- es-core/src/components/BadgesComponent.cpp | 2 +- es-core/src/components/BadgesComponent.h | 2 +- es-core/src/components/ImageComponent.h | 3 +-- es-core/src/resources/TextureResource.h | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index 0cd36f4e7..3516d8477 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -21,8 +21,7 @@ class VideoGameListView : public BasicGameListView { public: VideoGameListView(Window* window, FileData* root); - - virtual ~VideoGameListView() noexcept; + virtual ~VideoGameListView(); virtual void onShow() override; virtual void onThemeChanged(const std::shared_ptr& theme) override; diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 7bb184c14..eea4bad10 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -39,7 +39,7 @@ class GuiComponent public: GuiComponent(Window* window); - virtual ~GuiComponent() noexcept; + virtual ~GuiComponent(); virtual void textInput(const std::string& text); diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index e60471bb7..7ef09ec25 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -46,7 +46,7 @@ BadgesComponent::BadgesComponent(Window* window) mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); } -BadgesComponent::~BadgesComponent() noexcept +BadgesComponent::~BadgesComponent() { for (GuiComponent* c : mChildren) c->clearChildren(); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 27a642738..f4584ecf4 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -28,7 +28,7 @@ class BadgesComponent : public FlexboxComponent { public: BadgesComponent(Window* window); - ~BadgesComponent() noexcept; + ~BadgesComponent(); std::string getValue() const override; // Should be a list of strings. diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 474f46a1e..9e84bc84a 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -18,8 +18,7 @@ class ImageComponent : public GuiComponent { public: ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true); - - virtual ~ImageComponent() noexcept {}; + virtual ~ImageComponent() {}; void setDefaultImage(std::string path) { mDefaultPath = path; } diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 61abe0f55..39d096d6d 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -49,7 +49,7 @@ public: void rasterizeAt(size_t width, size_t height); glm::vec2 getSourceImageSize() const { return mSourceSize; } - virtual ~TextureResource() noexcept; + virtual ~TextureResource(); bool isInitialized() const { return true; } bool isTiled() const; From 1ed0e7e0f27b906da392c8ff7fa181bfcbb447d9 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 27 Sep 2021 21:16:56 +0200 Subject: [PATCH 027/128] Added a temporary alternative emulator badge graphics file. --- resources/graphics/badge_altemu.svg | 215 ++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 resources/graphics/badge_altemu.svg diff --git a/resources/graphics/badge_altemu.svg b/resources/graphics/badge_altemu.svg new file mode 100644 index 000000000..777ad8142 --- /dev/null +++ b/resources/graphics/badge_altemu.svg @@ -0,0 +1,215 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 50f2af007722240469614b3ae0847d7900f5d931 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 27 Sep 2021 21:27:07 +0200 Subject: [PATCH 028/128] Manual merges to align with master branch. --- CMakeLists.txt | 226 +++++------ CREDITS.md | 2 + USERGUIDE.md | 44 +-- es-app/CMakeLists.txt | 374 +++++++++--------- es-app/src/FileFilterIndex.h | 15 - es-app/src/FileSorts.h | 18 - es-app/src/guis/GuiLaunchScreen.h | 3 - es-app/src/guis/GuiScraperSearch.h | 15 - es-core/CMakeLists.txt | 274 ++++++------- es-core/src/GuiComponent.h | 2 - es-core/src/components/ButtonComponent.cpp | 1 + es-core/src/components/ButtonComponent.h | 8 - es-core/src/components/ComponentGrid.h | 14 - es-core/src/guis/GuiTextEditKeyboardPopup.cpp | 54 +-- es-core/src/guis/GuiTextEditKeyboardPopup.h | 8 - es-core/src/guis/GuiTextEditPopup.h | 3 - resources/systems/macos/es_systems.xml | 137 ++----- resources/systems/unix/es_systems.xml | 135 ++----- resources/systems/windows/es_systems.xml | 135 ++----- 19 files changed, 587 insertions(+), 881 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 15beefa1f..1c8ff03b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ set(LINUX_CPACK_GENERATOR "DEB" CACHE STRING "CPack generator, DEB or RPM") # Add local find modules to the CMake path. list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Utils - ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Packages) + ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Packages) # Define the options. option(GLES "Set to ON if targeting Embedded OpenGL" ${GLES}) @@ -36,11 +36,11 @@ option(CEC "Set to ON to enable CEC" ${CEC}) option(VLC_PLAYER "Set to ON to build the VLC-based video player" ${VLC_PLAYER}) option(CLANG_TIDY "Set to ON to build using the clang-tidy static analyzer" ${CLANG_TIDY}) -if (CLANG_TIDY) +if(CLANG_TIDY) find_program(CLANG_TIDY_BINARY NAMES clang-tidy) - if (CLANG_TIDY_BINARY STREQUAL "CLANG_TIDY_BINARY-NOTFOUND") + if(CLANG_TIDY_BINARY STREQUAL "CLANG_TIDY_BINARY-NOTFOUND") message("-- CLANG_TIDY was set but the clang-tidy binary was not found") - else () + else() message("-- Building with the clang-tidy static analyzer") set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*,\ -fuchsia-*,\ @@ -76,12 +76,12 @@ set_property(CACHE GLSYSTEM PROPERTY STRINGS "Desktop OpenGL" "Embedded OpenGL") #--------------------------------------------------------------------------------------------------- # Package dependencies. -if (GLSYSTEM MATCHES "Desktop OpenGL") +if(GLSYSTEM MATCHES "Desktop OpenGL") set(OpenGL_GL_PREFERENCE "GLVND") find_package(OpenGL REQUIRED) -else () +else() find_package(OpenGLES REQUIRED) -endif () +endif() # Skip package dependency checks if we're on Windows. if(NOT WIN32) @@ -103,73 +103,73 @@ if(CEC) endif() # Add ALSA for Linux. -if (CMAKE_SYSTEM_NAME MATCHES "Linux") +if(CMAKE_SYSTEM_NAME MATCHES "Linux") find_package(ALSA REQUIRED) -endif () +endif() #--------------------------------------------------------------------------------------------------- # Compiler and linker settings. -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") message("-- Compiler is Clang/LLVM") - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0) message(SEND_ERROR "You need at least Clang 5.0.0 to compile EmulationStation-DE") - endif () -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") message("-- Compiler is GNU/GCC") - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.1) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.1) message(SEND_ERROR "You need at least GCC 7.1 to compile EmulationStation-DE") - endif () - if (WIN32) + endif() + if(WIN32) set(CMAKE_CXX_FLAGS "-mwindows ${CMAKE_CXX_FLAGS}") - endif () -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") message("-- Compiler is MSVC") # If using the MSVC compiler on Windows, disable the built-in min() and max() macros. add_definitions(-DNOMINMAX) -endif () +endif() -if (CMAKE_BUILD_TYPE) +if(CMAKE_BUILD_TYPE) message("-- Build type is ${CMAKE_BUILD_TYPE}") -endif () +endif() # Set up compiler and linker flags for debug, profiling or release builds. if(CMAKE_BUILD_TYPE MATCHES Debug) # Enable the C++17 standard and disable optimizations as it's a debug build. - if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /Od /DEBUG:FULL") - else () + else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O0 -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0") - endif () + endif() # If using Clang, then add additional debug data needed by GDB. # Comment this out if you're using LLDB for debugging as this flag makes the binary # much larger and the application much slower. On macOS this setting is never enabled # as LLDB is the default debugger on this OS. - if (NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if(NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG") - endif () + endif() elseif(CMAKE_BUILD_TYPE MATCHES Profiling) # For the profiling build, we enable optimizations and supply the required profiler flags. - if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /O2 /DEBUG:FULL") - else () + else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -pg -g -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2 -pg") - endif () + endif() else() # Enable the C++17 standard and enable optimizations as it's a release build. # This will also disable all assert() macros. Strip the binary too. - if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG /std:c++17 /O2 /DEBUG:NONE") - else () + else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -DNDEBUG -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros") - if (APPLE) + if(APPLE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2") - else () + else() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2 -s") - endif () - endif () + endif() + endif() endif() # The following removes half of the ranlib warnings on macOS regarding no symbols for files @@ -180,26 +180,26 @@ if(APPLE) endif() if(APPLE) - if (MACOS_CODESIGN_IDENTITY) + if(MACOS_CODESIGN_IDENTITY) message("-- Code signing certificate identity: " ${MACOS_CODESIGN_IDENTITY}) - endif () - if (CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) + endif() + if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) message("-- macOS version 10.13 or lower has been set, so if code signing is enabled, Hardened Runtime will not be used") - endif () + endif() endif() #--------------------------------------------------------------------------------------------------- # Preprocessor directives. -if (GLSYSTEM MATCHES "Desktop OpenGL") +if(GLSYSTEM MATCHES "Desktop OpenGL") add_definitions(-DUSE_OPENGL_21) -else () +else() add_definitions(-DUSE_OPENGLES_10) -endif () +endif() -if (VLC_PLAYER) +if(VLC_PLAYER) add_definitions(-DBUILD_VLC_PLAYER) -endif () +endif() if(DEFINED BCMHOST OR RPI) add_definitions(-D_RPI_) @@ -217,13 +217,13 @@ add_definitions(-DGLM_FORCE_XYZW_ONLY) # we use /usr on Linux, /usr/pkg on NetBSD and /usr/local on FreeBSD and OpenBSD. if(NOT WIN32 AND NOT APPLE) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - if (CMAKE_SYSTEM_NAME MATCHES "Linux") + if(CMAKE_SYSTEM_NAME MATCHES "Linux") set(CMAKE_INSTALL_PREFIX "/usr" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") - elseif (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD") set(CMAKE_INSTALL_PREFIX "/usr/pkg" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") - else () + else() set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") - endif () + endif() endif() message("-- Installation prefix is set to " ${CMAKE_INSTALL_PREFIX}) add_definitions(-DES_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}") @@ -239,16 +239,16 @@ endif() # Include files. set(COMMON_INCLUDE_DIRS ${CURL_INCLUDE_DIR} - ${FFMPEG_INCLUDE_DIRS} - ${FreeImage_INCLUDE_DIRS} - ${FREETYPE_INCLUDE_DIRS} - ${PUGIXML_INCLUDE_DIRS} - ${RAPIDJSON_INCLUDE_DIRS} - ${SDL2_INCLUDE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/external/CImg - ${CMAKE_CURRENT_SOURCE_DIR}/external/glm - ${CMAKE_CURRENT_SOURCE_DIR}/external/nanosvg/src - ${CMAKE_CURRENT_SOURCE_DIR}/es-core/src) + ${FFMPEG_INCLUDE_DIRS} + ${FreeImage_INCLUDE_DIRS} + ${FREETYPE_INCLUDE_DIRS} + ${PUGIXML_INCLUDE_DIRS} + ${RAPIDJSON_INCLUDE_DIRS} + ${SDL2_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/external/CImg + ${CMAKE_CURRENT_SOURCE_DIR}/external/glm + ${CMAKE_CURRENT_SOURCE_DIR}/external/nanosvg/src + ${CMAKE_CURRENT_SOURCE_DIR}/es-core/src) if(VLC_PLAYER) set(COMMON_INCLUDE_DIRS ${COMMON_INCLUDE_DIRS} ${VLC_INCLUDE_DIR}) endif() @@ -276,69 +276,69 @@ if(DEFINED libCEC_FOUND) endif() # For Linux, add the ALSA include directory. -if (CMAKE_SYSTEM_NAME MATCHES "Linux") +if(CMAKE_SYSTEM_NAME MATCHES "Linux") list(APPEND COMMON_INCLUDE_DIRS ${ALSA_INCLUDE_DIRS}) -endif () +endif() -if (DEFINED BCMHOST OR RPI) +if(DEFINED BCMHOST OR RPI) list(APPEND COMMON_INCLUDE_DIRS "${CMAKE_FIND_ROOT_PATH}/opt/vc/include" - "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos" - "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vmcs_host/linux" - "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos/pthreads") -endif () + "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos" + "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vmcs_host/linux" + "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos/pthreads") +endif() #--------------------------------------------------------------------------------------------------- # Dependency libraries. -if (NOT WIN32) +if(NOT WIN32) set(COMMON_LIBRARIES ${CURL_LIBRARIES} - ${FFMPEG_LIBRARIES} - ${FreeImage_LIBRARIES} - ${FREETYPE_LIBRARIES} - ${PUGIXML_LIBRARIES} - ${SDL2_LIBRARY}) - if (VLC_PLAYER) + ${FFMPEG_LIBRARIES} + ${FreeImage_LIBRARIES} + ${FREETYPE_LIBRARIES} + ${PUGIXML_LIBRARIES} + ${SDL2_LIBRARY}) + if(VLC_PLAYER) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${VLC_LIBRARIES}) - endif () -elseif (WIN32) - if (DEFINED MSVC) + endif() +elseif(WIN32) + if(DEFINED MSVC) set(COMMON_LIBRARIES "${PROJECT_SOURCE_DIR}/avcodec.lib" - "${PROJECT_SOURCE_DIR}/avfilter.lib" - "${PROJECT_SOURCE_DIR}/avformat.lib" - "${PROJECT_SOURCE_DIR}/avutil.lib" - "${PROJECT_SOURCE_DIR}/swresample.lib" - "${PROJECT_SOURCE_DIR}/swscale.lib" - "${PROJECT_SOURCE_DIR}/FreeImage.lib" - "${PROJECT_SOURCE_DIR}/glew32.lib" - "${PROJECT_SOURCE_DIR}/libcurl-x64.lib" - "${PROJECT_SOURCE_DIR}/freetype.lib" - "${PROJECT_SOURCE_DIR}/pugixml.lib" - "${PROJECT_SOURCE_DIR}/SDL2main.lib" - "${PROJECT_SOURCE_DIR}/SDL2.lib" - "Winmm.dll") - if (VLC_PLAYER) + "${PROJECT_SOURCE_DIR}/avfilter.lib" + "${PROJECT_SOURCE_DIR}/avformat.lib" + "${PROJECT_SOURCE_DIR}/avutil.lib" + "${PROJECT_SOURCE_DIR}/swresample.lib" + "${PROJECT_SOURCE_DIR}/swscale.lib" + "${PROJECT_SOURCE_DIR}/FreeImage.lib" + "${PROJECT_SOURCE_DIR}/glew32.lib" + "${PROJECT_SOURCE_DIR}/libcurl-x64.lib" + "${PROJECT_SOURCE_DIR}/freetype.lib" + "${PROJECT_SOURCE_DIR}/pugixml.lib" + "${PROJECT_SOURCE_DIR}/SDL2main.lib" + "${PROJECT_SOURCE_DIR}/SDL2.lib" + "Winmm.dll") + if(VLC_PLAYER) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "${PROJECT_SOURCE_DIR}/libvlc.lib") - endif () - else () + endif() + else() set(COMMON_LIBRARIES "${PROJECT_SOURCE_DIR}/avcodec-58.dll" - "${PROJECT_SOURCE_DIR}/avfilter-7.dll" - "${PROJECT_SOURCE_DIR}/avformat-58.dll" - "${PROJECT_SOURCE_DIR}/avutil-56.dll" - "${PROJECT_SOURCE_DIR}/swresample-3.dll" - "${PROJECT_SOURCE_DIR}/swscale-5.dll" - "${PROJECT_SOURCE_DIR}/FreeImage.dll" - "${PROJECT_SOURCE_DIR}/glew32.dll" - "${PROJECT_SOURCE_DIR}/libcurl-x64.dll" - "${PROJECT_SOURCE_DIR}/libfreetype.dll" - "${PROJECT_SOURCE_DIR}/libpugixml.dll" - "${PROJECT_SOURCE_DIR}/libSDL2main.a" - "${PROJECT_SOURCE_DIR}/SDL2.dll" - "mingw32" - "Winmm.dll") - if (VLC_PLAYER) + "${PROJECT_SOURCE_DIR}/avfilter-7.dll" + "${PROJECT_SOURCE_DIR}/avformat-58.dll" + "${PROJECT_SOURCE_DIR}/avutil-56.dll" + "${PROJECT_SOURCE_DIR}/swresample-3.dll" + "${PROJECT_SOURCE_DIR}/swscale-5.dll" + "${PROJECT_SOURCE_DIR}/FreeImage.dll" + "${PROJECT_SOURCE_DIR}/glew32.dll" + "${PROJECT_SOURCE_DIR}/libcurl-x64.dll" + "${PROJECT_SOURCE_DIR}/libfreetype.dll" + "${PROJECT_SOURCE_DIR}/libpugixml.dll" + "${PROJECT_SOURCE_DIR}/libSDL2main.a" + "${PROJECT_SOURCE_DIR}/SDL2.dll" + "mingw32" + "Winmm.dll") + if(VLC_PLAYER) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "${PROJECT_SOURCE_DIR}/libvlc.dll") - endif () - endif () + endif() + endif() endif() if(APPLE) @@ -366,9 +366,9 @@ if(DEFINED libCEC_FOUND) endif() # Add ALSA for Linux libraries. -if (CMAKE_SYSTEM_NAME MATCHES "Linux") +if(CMAKE_SYSTEM_NAME MATCHES "Linux") list(APPEND COMMON_LIBRARIES ${ALSA_LIBRARY}) -endif () +endif() if(DEFINED BCMHOST) link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vc/lib") @@ -378,11 +378,11 @@ elseif(RPI) list(APPEND COMMON_LIBRARIES ${OPENGLES_LIBRARIES}) endif() -if (GLSYSTEM MATCHES "Desktop OpenGL") +if(GLSYSTEM MATCHES "Desktop OpenGL") list(APPEND COMMON_LIBRARIES ${OPENGL_LIBRARIES}) -else () +else() list(APPEND COMMON_LIBRARIES EGL ${OPENGLES_LIBRARIES}) -endif () +endif() #--------------------------------------------------------------------------------------------------- # Build directories. diff --git a/CREDITS.md b/CREDITS.md index 770648677..60cac7b99 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -60,6 +60,7 @@ https://rapidjson.org SDL \ https://www.libsdl.org + # Code Some code (like the virtual keyboard) was borrowed from Batocera.linux \ @@ -71,6 +72,7 @@ https://www.bzflag.org A few of the GLSL shaders were borrowed from the RetroArch project \ https://www.retroarch.com + # Resources Akrobat font \ diff --git a/USERGUIDE.md b/USERGUIDE.md index 97bab78ff..144d5ba26 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -377,8 +377,7 @@ The platform name for the Commodore 64 is `c64`, so the following structure woul ~/ROMs/c64/Multidisk/Pirates/Pirates!.m3u ``` -It's highly recommended to create `.m3u` playlist files for multi-disc images as this normally automates disk swapping -in the emulator. It's then this .m3u file that should be selected for launching the game. +It's highly recommended to create `.m3u` playlist files for multi-disc images as this normally automates disk swapping in the emulator. It's then this .m3u file that should be selected for launching the game. The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2.m3u: @@ -431,13 +430,9 @@ Apart from the potential difficulty in locating the emulator binary, there are s #### Commodore Amiga -There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard -disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play -Amiga games as any console with game ROMs. +There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play Amiga games as any console with game ROMs. -An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can -either put single-disk images in the root folder or in a dedicated adf directory, or multiple-disk games in separate -folders. It's highly recommended to create `.m3u` playlist files for multi-disc images as described earlier. +An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can either put single-disk images in the root folder or in a dedicated adf directory, or multiple-disk games in separate folders. It's highly recommended to create `.m3u` playlist files for multi-disc images as described earlier. Here's an example of what the file structure could look like: @@ -877,8 +872,7 @@ If this setting is enabled and a folder has its flag set to be excluded from the **Scrape actual folders** _(Multi-scraper only)_ -Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any -multi-disc games where there is a folder for each individual game. +Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any multi-disc games where there is a folder for each individual game. **Auto-retry on peer verification errors** _(ScreenScraper only)_ @@ -1225,11 +1219,7 @@ If this option is disabled, hidden files and folders within the ROMs directory t **Show hidden games (requires restart)** -You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want -to see some batch files and executables inside ES-DE, or for multi-disc games where you may only want to show the .m3u -playlists and not the individual game files. By disabling this option these files will not be processed at all when -ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in -the gamelist view to visually indicate that they are hidden. +You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want to see some batch files and executables inside ES-DE, or for multi-disc games where you may only want to show the .m3u playlists and not the individual game files. By disabling this option these files will not be processed at all when ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in the gamelist view to visually indicate that they are hidden. **Enable custom event scripts** @@ -1417,11 +1407,7 @@ A flag to mark whether the game is suitable for children. This will be applied a **Hidden** -A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not -be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game -files for multi-disc games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games -has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden -entry. +A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game files for multi-disc games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden entry. **Broken/not working** @@ -1429,27 +1415,15 @@ A flag to indicate whether the game is broken. Useful for MAME games for instanc **Exclude from game counter** _(files only)_ -A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be -included in the game counter shown per system on the system view, and it will not be included in the system information -field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite -useful for multi-file games such as multi-disc Amiga or Commodore 64 games, or for DOS games where you want to exclude -setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this -flag set will have a lower opacity in the gamelists, making them easy to spot. +A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be included in the game counter shown per system on the system view, and it will not be included in the system information field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite useful for multi-file games such as multi-disc Amiga or Commodore 64 games, or for DOS games where you want to exclude setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this flag set will have a lower opacity in the gamelists, making them easy to spot. **Exclude from multi-scraper** -Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for -multi-disc games for example. There is an option in the scraper settings to ignore this flag, but by default the -multi-scraper will respect it. +Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for multi-disc games for example. There is an option in the scraper settings to ignore this flag, but by default the multi-scraper will respect it. **Hide metadata fields** -This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for -situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or -INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u -playlist should have any metadata values. The only fields shown with this option enabled are the game name and -description. Using the description it's possible to write some comments regarding the file or folder, should you want -to. It's also possible to display game images and videos with this setting enabled. +This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. **Launch command** _(files only)_ diff --git a/es-app/CMakeLists.txt b/es-app/CMakeLists.txt index 9dbb9681e..9f4dbe7e1 100644 --- a/es-app/CMakeLists.txt +++ b/es-app/CMakeLists.txt @@ -10,106 +10,106 @@ project("emulationstation-de") set(ES_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h - # GUIs - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h + # GUIs + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h - # Scrapers - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.h + # Scrapers + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.h - # Views - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h - ) + # Views + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h +) set(ES_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp - # GUIs - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp + # GUIs + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp - # Scrapers - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.cpp + # Scrapers + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.cpp - # Views - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp - ) + # Views + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp +) if(WIN32) LIST(APPEND ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation.rc) @@ -134,55 +134,55 @@ endif() # Setup for installation and package generation. if(WIN32) install(TARGETS EmulationStation RUNTIME DESTINATION .) - if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") install(FILES ../avcodec-58.dll - ../avfilter-7.dll - ../avformat-58.dll - ../avutil-56.dll - ../postproc-55.dll - ../swresample-3.dll - ../swscale-5.dll - ../FreeImage.dll - ../freetype.dll - ../glew32.dll - ../libcrypto-1_1-x64.dll - ../libcurl-x64.dll - ../libssl-1_1-x64.dll - ../MSVCP140.dll - ../pugixml.dll - ../SDL2.dll - ../VCOMP140.DLL - ../VCRUNTIME140.dll - ../VCRUNTIME140_1.dll - DESTINATION .) - if (VLC_PLAYER) + ../avfilter-7.dll + ../avformat-58.dll + ../avutil-56.dll + ../postproc-55.dll + ../swresample-3.dll + ../swscale-5.dll + ../FreeImage.dll + ../freetype.dll + ../glew32.dll + ../libcrypto-1_1-x64.dll + ../libcurl-x64.dll + ../libssl-1_1-x64.dll + ../MSVCP140.dll + ../pugixml.dll + ../SDL2.dll + ../VCOMP140.DLL + ../VCRUNTIME140.dll + ../VCRUNTIME140_1.dll + DESTINATION .) + if(VLC_PLAYER) install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .) - endif () - else () + endif() + else() install(FILES ../avcodec-58.dll - ../avfilter-7.dll - ../avformat-58.dll - ../avutil-56.dll - ../postproc-55.dll - ../swresample-3.dll - ../swscale-5.dll - ../FreeImage.dll - ../glew32.dll - ../libcrypto-1_1-x64.dll - ../libcurl-x64.dll - ../libfreetype.dll - ../libpugixml.dll - ../libssl-1_1-x64.dll - ../SDL2.dll - ../vcomp140.dll - DESTINATION .) - if (VLC_PLAYER) + ../avfilter-7.dll + ../avformat-58.dll + ../avutil-56.dll + ../postproc-55.dll + ../swresample-3.dll + ../swscale-5.dll + ../FreeImage.dll + ../glew32.dll + ../libcrypto-1_1-x64.dll + ../libcurl-x64.dll + ../libfreetype.dll + ../libpugixml.dll + ../libssl-1_1-x64.dll + ../SDL2.dll + ../vcomp140.dll + DESTINATION .) + if(VLC_PLAYER) install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .) - endif () - endif () - if (VLC_PLAYER) + endif() + endif() + if(VLC_PLAYER) install(DIRECTORY ${CMAKE_SOURCE_DIR}/plugins DESTINATION .) - endif () + endif() install(FILES ../LICENSE DESTINATION .) install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION .) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes DESTINATION .) @@ -206,81 +206,81 @@ elseif(APPLE) # on your system (e.g. if using libSDL2-2.1.0.dylib instead of libSDL2-2.0.0.dylib). # This problem definitely needs to be resolved properly at a later date. add_custom_command(TARGET EmulationStation POST_BUILD COMMAND ${CMAKE_INSTALL_NAME_TOOL} - -change /usr/local/lib/libavcodec.58.dylib @rpath/libavcodec.58.dylib - -change /usr/local/lib/libavfilter.7.dylib @rpath/libavfilter.7.dylib - -change /usr/local/lib/libavformat.58.dylib @rpath/libavformat.58.dylib - -change /usr/local/lib/libavutil.56.dylib @rpath/libavutil.56.dylib - -change /usr/local/lib/libswresample.3.dylib @rpath/libswresample.3.dylib - -change /usr/local/lib/libswscale.5.dylib @rpath/libswscale.5.dylib - -change /usr/local/opt/freeimage/lib/libfreeimage.dylib @rpath/libfreeimage.dylib - -change /usr/local/opt/freetype/lib/libfreetype.6.dylib @rpath/libfreetype.6.dylib - -change /usr/local/opt/libpng/lib/libpng16.16.dylib @rpath/libpng16.16.dylib - -change /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib @rpath/libSDL2-2.0.0.dylib - $) + -change /usr/local/lib/libavcodec.58.dylib @rpath/libavcodec.58.dylib + -change /usr/local/lib/libavfilter.7.dylib @rpath/libavfilter.7.dylib + -change /usr/local/lib/libavformat.58.dylib @rpath/libavformat.58.dylib + -change /usr/local/lib/libavutil.56.dylib @rpath/libavutil.56.dylib + -change /usr/local/lib/libswresample.3.dylib @rpath/libswresample.3.dylib + -change /usr/local/lib/libswscale.5.dylib @rpath/libswscale.5.dylib + -change /usr/local/opt/freeimage/lib/libfreeimage.dylib @rpath/libfreeimage.dylib + -change /usr/local/opt/freetype/lib/libfreetype.6.dylib @rpath/libfreetype.6.dylib + -change /usr/local/opt/libpng/lib/libpng16.16.dylib @rpath/libpng16.16.dylib + -change /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib @rpath/libSDL2-2.0.0.dylib + $) set(APPLE_DYLIB_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE) + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) install(FILES ${CMAKE_SOURCE_DIR}/libavcodec.58.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libavfilter.7.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libavformat.58.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libavutil.56.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libpostproc.55.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libswresample.3.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libswscale.5.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libfdk-aac.2.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libfreeimage.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libfreetype.6.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libpng16.16.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libSDL2-2.0.0.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) - if (VLC_PLAYER) + if(VLC_PLAYER) install(FILES ${CMAKE_SOURCE_DIR}/libvlc.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(FILES ${CMAKE_SOURCE_DIR}/libvlccore.dylib - PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) + PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) install(DIRECTORY ${CMAKE_SOURCE_DIR}/plugins - DESTINATION ../MacOS) - endif () + DESTINATION ../MacOS) + endif() install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ../Resources) install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources DESTINATION ../Resources) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes DESTINATION ../Resources) install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION ../Resources) -else () +else() install(TARGETS emulationstation RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) - if (CMAKE_SYSTEM_NAME MATCHES "Linux") + if(CMAKE_SYSTEM_NAME MATCHES "Linux") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6) - else () + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6) + else() install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz - DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man6) - endif () + DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man6) + endif() install(FILES ${CMAKE_SOURCE_DIR}/LICENSE - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.desktop - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.svg - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pixmaps) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pixmaps) install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) endif() include(InstallRequiredSystemLibraries) @@ -317,13 +317,13 @@ endif() # Settings per operating system and generator type. if(APPLE) set(CPACK_GENERATOR "Bundle") - if (CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) + if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) set(CPACK_PACKAGE_FILE_NAME "EmulationStation-DE-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}_legacy") set(CPACK_DMG_VOLUME_NAME "EmulationStation Desktop Edition ${CPACK_PACKAGE_VERSION}_legacy") - else () + else() set(CPACK_PACKAGE_FILE_NAME "EmulationStation-DE-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}") set(CPACK_DMG_VOLUME_NAME "EmulationStation Desktop Edition ${CPACK_PACKAGE_VERSION}") - endif () + endif() set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE.icns") set(CPACK_DMG_DS_STORE "${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE_DS_Store") set(CPACK_BUNDLE_NAME "EmulationStation Desktop Edition") @@ -331,9 +331,9 @@ if(APPLE) set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE_Info.plist") if(MACOS_CODESIGN_IDENTITY) set(CPACK_BUNDLE_APPLE_CERT_APP "Developer ID Application: ${MACOS_CODESIGN_IDENTITY}") - if (CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER 10.13) + if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER 10.13) set(CPACK_BUNDLE_APPLE_CODESIGN_PARAMETER "--deep --force --options runtime") - endif () + endif() endif() elseif(WIN32) set(CPACK_GENERATOR "NSIS") @@ -354,9 +354,9 @@ elseif(WIN32) else() set(CPACK_PACKAGE_INSTALL_DIRECTORY "emulationstation_${CMAKE_PACKAGE_VERSION}") set(CPACK_PACKAGE_EXECUTABLES "emulationstation" "emulationstation") - if (LINUX_CPACK_GENERATOR STREQUAL "DEB") + if(LINUX_CPACK_GENERATOR STREQUAL "DEB") set(CPACK_GENERATOR "DEB") - endif () + endif() set(CPACK_DEBIAN_FILE_NAME "emulationstation-de-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.deb") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Leon Styhre ") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://es-de.org") @@ -366,9 +366,9 @@ else() set(CPACK_DEBIAN_PACKAGE_DEPENDS "vlc") endif() set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) - if (LINUX_CPACK_GENERATOR STREQUAL "RPM") + if(LINUX_CPACK_GENERATOR STREQUAL "RPM") set(CPACK_GENERATOR "RPM") - endif () + endif() set(CPACK_RPM_FILE_NAME "emulationstation-de-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.rpm") set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) set(CPACK_RPM_PACKAGE_LICENSE "MIT") diff --git a/es-app/src/FileFilterIndex.h b/es-app/src/FileFilterIndex.h index a9a127ad1..d4d744a7f 100644 --- a/es-app/src/FileFilterIndex.h +++ b/es-app/src/FileFilterIndex.h @@ -50,37 +50,22 @@ class FileFilterIndex public: FileFilterIndex(); ~FileFilterIndex(); - void addToIndex(FileData* game); - void removeFromIndex(FileData* game); - void setFilter(FilterIndexType type, std::vector* values); - void setTextFilter(std::string textFilter); - std::string getTextFilter() { return mTextFilter; } - void clearAllFilters(); - void debugPrintIndexes(); - bool showFile(FileData* game); - bool isFiltered(); - bool isKeyBeingFilteredBy(std::string key, FilterIndexType type); - std::vector& getFilterDataDecls() { return filterDataDecl; } - void setTextRemoveSystem(bool status) { mTextRemoveSystem = status; } void importIndex(FileFilterIndex* indexToImport); - void resetIndex(); - void resetFilters(); - void setKidModeFilters(); private: diff --git a/es-app/src/FileSorts.h b/es-app/src/FileSorts.h index 424acf109..e7f84346d 100644 --- a/es-app/src/FileSorts.h +++ b/es-app/src/FileSorts.h @@ -18,41 +18,23 @@ namespace FileSorts { bool compareName(const FileData* file1, const FileData* file2); bool compareNameDescending(const FileData* file1, const FileData* file2); - bool compareRating(const FileData* file1, const FileData* file2); - bool compareRatingDescending(const FileData* file1, const FileData* file2); - bool compareReleaseDate(const FileData* file1, const FileData* file2); - bool compareReleaseDateDescending(const FileData* file1, const FileData* file2); - bool compareDeveloper(const FileData* file1, const FileData* file2); - bool compareDeveloperDescending(const FileData* file1, const FileData* file2); - bool comparePublisher(const FileData* file1, const FileData* file2); - bool comparePublisherDescending(const FileData* file1, const FileData* file2); - bool compareGenre(const FileData* file1, const FileData* file2); - bool compareGenreDescending(const FileData* file1, const FileData* file2); - bool compareNumPlayers(const FileData* file1, const FileData* file2); - bool compareNumPlayersDescending(const FileData* file1, const FileData* file2); - bool compareLastPlayed(const FileData* file1, const FileData* file2); - bool compareLastPlayedDescending(const FileData* file1, const FileData* file2); - bool compareTimesPlayed(const FileData* file1, const FileData* fil2); - bool compareTimesPlayedDescending(const FileData* file1, const FileData* fil2); - bool compareSystem(const FileData* file1, const FileData* file2); - bool compareSystemDescending(const FileData* file1, const FileData* file2); extern const std::vector SortTypes; diff --git a/es-app/src/guis/GuiLaunchScreen.h b/es-app/src/guis/GuiLaunchScreen.h index dac32f24f..c40d3c220 100644 --- a/es-app/src/guis/GuiLaunchScreen.h +++ b/es-app/src/guis/GuiLaunchScreen.h @@ -22,17 +22,14 @@ class GuiLaunchScreen : public Window::GuiLaunchScreen, GuiComponent { public: GuiLaunchScreen(Window* window); - virtual ~GuiLaunchScreen(); virtual void displayLaunchScreen(FileData* game) override; - virtual void closeLaunchScreen() override; void onSizeChanged() override; virtual void update(int deltaTime) override; - virtual void render(const glm::mat4& parentTrans) override; private: diff --git a/es-app/src/guis/GuiScraperSearch.h b/es-app/src/guis/GuiScraperSearch.h index fb824360e..449124ba0 100644 --- a/es-app/src/guis/GuiScraperSearch.h +++ b/es-app/src/guis/GuiScraperSearch.h @@ -63,27 +63,20 @@ public: { mAcceptCallback = acceptCallback; } - void setSkipCallback(const std::function& skipCallback) { mSkipCallback = skipCallback; } - void setCancelCallback(const std::function& cancelCallback) { mCancelCallback = cancelCallback; } bool input(InputConfig* config, Input input) override; - void update(int deltaTime) override; - void render(const glm::mat4& parentTrans) override; - std::vector getHelpPrompts() override; - HelpStyle getHelpStyle() override; - void onSizeChanged() override; void decreaseScrapeCount() @@ -91,26 +84,18 @@ public: if (mScrapeCount > 0) mScrapeCount--; } - void unsetRefinedSearch() { mRefinedSearch = false; } - bool getRefinedSearch() { return mRefinedSearch; } - bool getFoundGame() { return mFoundGame; } - const std::string& getNameOverride() { return mLastSearch.nameOverride; } void onFocusGained() override { mGrid.onFocusGained(); } - void onFocusLost() override { mGrid.onFocusLost(); } private: void updateViewStyle(); - void updateThumbnail(); - void updateInfoPane(); - void resizeMetadata(); void onSearchError(const std::string& error, diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index d906217a7..92a164761 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -9,157 +9,157 @@ project("core") set(CORE_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/src/AsyncHandle.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/CECInput.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/HelpStyle.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Log.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/MameNames.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Platform.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Window.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/AsyncHandle.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/CECInput.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/HelpStyle.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Log.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MameNames.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Platform.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Window.h - # Animations - ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/Animation.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/AnimationController.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/LambdaAnimation.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/MoveCameraAnimation.h + # Animations + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/Animation.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/AnimationController.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/LambdaAnimation.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/MoveCameraAnimation.h - # GUI components - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageGridComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/MenuComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/OptionListComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/RatingComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollableContainer.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextListComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoFFmpegComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.h + # GUI components + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageGridComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/MenuComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/OptionListComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/RatingComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollableContainer.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextListComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoFFmpegComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.h - # GUIs - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditKeyboardPopup.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.h + # GUIs + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditKeyboardPopup.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.h - # Renderers - ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Renderer.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Shader_GL21.h + # Renderers + ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Renderer.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Shader_GL21.h - # Resources - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.h + # Resources + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.h - # Utils - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/CImgUtil.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FileSystemUtil.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MathUtil.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.h - ) + # Utils + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/CImgUtil.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FileSystemUtil.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MathUtil.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.h +) set(CORE_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/CECInput.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/HelpStyle.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Log.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/MameNames.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Platform.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Scripting.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/CECInput.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/HelpStyle.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Log.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MameNames.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Platform.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Scripting.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp - # Animations - ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/AnimationController.cpp + # Animations + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/AnimationController.cpp - # GUI components - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/MenuComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/RatingComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollableContainer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoFFmpegComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.cpp + # GUI components + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/MenuComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/RatingComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollableContainer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoFFmpegComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.cpp - # GUIs - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditKeyboardPopup.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.cpp + # GUIs + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditKeyboardPopup.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.cpp - # Renderer - ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Renderer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Renderer_GL21.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Renderer_GLES10.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Shader_GL21.cpp + # Renderer + ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Renderer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Renderer_GL21.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Renderer_GLES10.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/renderers/Shader_GL21.cpp - # Resources - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.cpp + # Resources + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.cpp - # Utils - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/CImgUtil.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FileSystemUtil.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MathUtil.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.cpp - ) + # Utils + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/CImgUtil.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FileSystemUtil.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MathUtil.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.cpp +) include_directories(${COMMON_INCLUDE_DIRS}) add_library(es-core STATIC ${CORE_SOURCES} ${CORE_HEADERS}) diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index eea4bad10..322efab40 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -38,7 +38,6 @@ class GuiComponent { public: GuiComponent(Window* window); - virtual ~GuiComponent(); virtual void textInput(const std::string& text); @@ -232,7 +231,6 @@ public: protected: void renderChildren(const glm::mat4& transform) const; - void updateSelf(int deltaTime); // Updates animations. void updateChildren(int deltaTime); // Updates animations. diff --git a/es-core/src/components/ButtonComponent.cpp b/es-core/src/components/ButtonComponent.cpp index d7ffd4d23..3143f52a9 100644 --- a/es-core/src/components/ButtonComponent.cpp +++ b/es-core/src/components/ButtonComponent.cpp @@ -29,6 +29,7 @@ ButtonComponent::ButtonComponent(Window* window, , mTextColorUnfocused{0x777777FF} , mFlatColorFocused{0x878787FF} , mFlatColorUnfocused{0x60606025} + { setPressedFunc(func); setText(text, helpText, upperCase); diff --git a/es-core/src/components/ButtonComponent.h b/es-core/src/components/ButtonComponent.h index 3affa67f2..55ad497db 100644 --- a/es-core/src/components/ButtonComponent.h +++ b/es-core/src/components/ButtonComponent.h @@ -25,38 +25,30 @@ public: bool flatStyle = false); void onSizeChanged() override; - void onFocusGained() override; - void onFocusLost() override; void setText(const std::string& text, const std::string& helpText, bool upperCase = true); - const std::string& getText() const { return mText; } void setPressedFunc(std::function f) { mPressedFunc = f; } - void setEnabled(bool state) override; void setPadding(const glm::vec4 padding); - glm::vec4 getPadding() { return mPadding; } void setFlatColorFocused(unsigned int color) { mFlatColorFocused = color; } - void setFlatColorUnfocused(unsigned int color) { mFlatColorUnfocused = color; } const std::function& getPressedFunc() const { return mPressedFunc; } bool input(InputConfig* config, Input input) override; - void render(const glm::mat4& parentTrans) override; virtual std::vector getHelpPrompts() override; private: unsigned int getCurTextColor() const; - void updateImage(); NinePatchComponent mBox; diff --git a/es-core/src/components/ComponentGrid.h b/es-core/src/components/ComponentGrid.h index 94300ae68..fc1145108 100644 --- a/es-core/src/components/ComponentGrid.h +++ b/es-core/src/components/ComponentGrid.h @@ -34,7 +34,6 @@ class ComponentGrid : public GuiComponent { public: ComponentGrid(Window* window, const glm::ivec2& gridDimensions); - virtual ~ComponentGrid(); bool removeEntry(const std::shared_ptr& comp); @@ -53,35 +52,26 @@ public: } void textInput(const std::string& text) override; - bool input(InputConfig* config, Input input) override; - void update(int deltaTime) override; - void render(const glm::mat4& parentTrans) override; - void onSizeChanged() override; void resetCursor(); - bool cursorValid(); float getColWidth(int col); - float getRowHeight(int row); // If update is false, will not call an onSizeChanged() which triggers // a (potentially costly) repositioning + resizing of every element. void setColWidthPerc(int col, float width, bool update = true); - // Dito. void setRowHeightPerc(int row, float height, bool update = true); bool moveCursor(glm::ivec2 dir); - // Pass -1 for xPos or yPos to keep its axis cursor position. void moveCursorTo(int xPos, int yPos, bool selectLeftCell = false); - void setCursorTo(const std::shared_ptr& comp); std::shared_ptr getSelectedComponent() @@ -94,7 +84,6 @@ public: } void onFocusLost() override; - void onFocusGained() override; virtual std::vector getHelpPrompts() override; @@ -133,13 +122,10 @@ private: // Update position and size. void updateCellComponent(const GridEntry& cell); - void updateSeparators(); void onCursorMoved(glm::ivec2 from, glm::ivec2 to); - const GridEntry* getCellAt(int x, int y) const; - const GridEntry* getCellAt(const glm::ivec2& pos) const { return getCellAt(pos.x, pos.y); } std::vector> mSeparators; diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp index 504fc942c..b3f46a3d3 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp @@ -39,38 +39,38 @@ #include "utils/StringUtil.h" // clang-format off -std::vector> kbBaseUS{ - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "DEL"}, - {"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "DEL"}, - {"¡", "²", "³", "¤", "€", "¼", "½", "¾", "‘", "’", "¥", "×", "DEL"}, - {"¹", "", "", "£", "", "", "", "", "", "", "", "÷", "DEL"}, +std::vector> kbBaseUS{ + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "DEL"}, + {"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "DEL"}, + {"¡", "²", "³", "¤", "€", "¼", "½", "¾", "‘", "’", "¥", "×", "DEL"}, + {"¹", "", "", "£", "", "", "", "", "", "", "", "÷", "DEL"}, - {"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "OK"}, - {"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "OK"}, - {"ä", "å", "é", "®", "þ", "ü", "ú", "í", "ó", "ö", "«", "»", "OK"}, - {"Ä", "Å", "É", "", "Þ", "Ü", "Ú", "Í", "Ó", "Ö", "", "", "OK"}, + {"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "OK"}, + {"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "OK"}, + {"ä", "å", "é", "®", "þ", "ü", "ú", "í", "ó", "ö", "«", "»", "OK"}, + {"Ä", "Å", "É", "", "Þ", "Ü", "Ú", "Í", "Ó", "Ö", "", "", "OK"}, - {"a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\\", "-rowspan-"}, - {"A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "|", "-rowspan-"}, - {"á", "ß", "ð", "", "", "", "", "", "ø", "¶", "´", "¬", "-rowspan-"}, - {"Á", "§", "Ð", "", "", "", "", "", "Ø", "°", "¨", "¦", "-rowspan-"}, + {"a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\\", "-rowspan-"}, + {"A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "|", "-rowspan-"}, + {"á", "ß", "ð", "", "", "", "", "", "ø", "¶", "´", "¬", "-rowspan-"}, + {"Á", "§", "Ð", "", "", "", "", "", "Ø", "°", "¨", "¦", "-rowspan-"}, - {"`", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "ALT", "-colspan-"}, - {"~", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "ALT", "-colspan-"}, - {"", "æ", "", "©", "", "", "ñ", "µ", "ç", "", "¿", "ALT", "-colspan-"}, - {"", "Æ", "", "¢", "", "", "Ñ", "Μ", "Ç", "", "", "ALT", "-colspan-"}}; + {"`", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "ALT", "-colspan-"}, + {"~", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "ALT", "-colspan-"}, + {"", "æ", "", "©", "", "", "ñ", "µ", "ç", "", "¿", "ALT", "-colspan-"}, + {"", "Æ", "", "¢", "", "", "Ñ", "Μ", "Ç", "", "", "ALT", "-colspan-"}}; -std::vector> kbLastRowNormal{ - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; +std::vector> kbLastRowNormal{ + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; -std::vector> kbLastRowLoad{ - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, - {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; +std::vector> kbLastRowLoad{ + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}, + {"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}}; // clang-format on GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.h b/es-core/src/guis/GuiTextEditKeyboardPopup.h index 6ec0ac8b4..b8660fdf0 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.h +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.h @@ -33,13 +33,10 @@ public: const std::string& cancelBtnHelpText = "DISCARD CHANGES"); void onSizeChanged() override; - bool input(InputConfig* config, Input input) override; - void update(int deltaTime) override; std::vector getHelpPrompts() override; - HelpStyle getHelpStyle() override { return mHelpStyle; } private: @@ -51,7 +48,6 @@ private: const std::string shiftedKey; const std::string altedKey; const std::string altshiftedKey; - KeyboardButton(const std::shared_ptr b, const std::string& k, const std::string& sk, @@ -65,20 +61,16 @@ private: }; void updateDeleteRepeat(int deltaTime); - void updateNavigationRepeat(int deltaTime); void shiftKeys(); - void altKeys(); - void altShiftKeys(); std::shared_ptr makeButton(const std::string& key, const std::string& shiftedKey, const std::string& altedKey, const std::string& altshiftedKey); - std::vector mKeyboardButtons; std::shared_ptr mShiftButton; diff --git a/es-core/src/guis/GuiTextEditPopup.h b/es-core/src/guis/GuiTextEditPopup.h index 70c473f2c..a77623704 100644 --- a/es-core/src/guis/GuiTextEditPopup.h +++ b/es-core/src/guis/GuiTextEditPopup.h @@ -33,13 +33,10 @@ public: const std::string& cancelBtnHelpText = "DISCARD CHANGES"); void onSizeChanged() override; - bool input(InputConfig* config, Input input) override; - void update(int deltaTime) override; std::vector getHelpPrompts() override; - HelpStyle getHelpStyle() override { return mHelpStyle; } private: diff --git a/resources/systems/macos/es_systems.xml b/resources/systems/macos/es_systems.xml index 85064121e..ffe09a5d2 100644 --- a/resources/systems/macos/es_systems.xml +++ b/resources/systems/macos/es_systems.xml @@ -99,12 +99,10 @@ .cmd .CMD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% arcade arcade @@ -222,15 +220,10 @@ c64 Commodore 64 %ROMPATH%/c64 - .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 - .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ - .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64sc_libretro.dylib %ROM% - + .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64sc_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_xscpu64_libretro.dylib %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_xscpu64_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x128_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/frodo_libretro.dylib %ROM% c64 @@ -363,20 +356,11 @@ FinalBurn Alpha %ROMPATH%/fba .iso .ISO .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% - - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%/fbalpha2012_neogeo_libretro.dylib %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.dylib - %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps2_libretro.dylib - %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps3_libretro.dylib - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_neogeo_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps2_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps3_libretro.dylib %ROM% arcade fba @@ -472,14 +456,9 @@ genesis Sega Genesis %ROMPATH%/genesis - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx - .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% - - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/blastem_libretro.dylib %ROM% genesis @@ -544,14 +523,12 @@ Multiple Arcade Machine Emulator %ROMPATH%/mame .cmd .CMD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.dylib %ROM% arcade mame @@ -577,14 +554,9 @@ mastersystem Sega Master System %ROMPATH%/mastersystem - .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md - .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% - - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/smsplus_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/gearsystem_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.dylib %ROM% @@ -617,14 +589,9 @@ megadrive Sega Mega Drive %ROMPATH%/megadrive - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx - .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% - - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/blastem_libretro.dylib %ROM% megadrive @@ -882,13 +849,9 @@ pcengine NEC PC Engine %ROMPATH%/pcengine - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib %ROM% pcengine pcengine @@ -896,13 +859,9 @@ pcenginecd NEC PC Engine CD %ROMPATH%/pcenginecd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib %ROM% pcenginecd pcenginecd @@ -982,14 +941,10 @@ psx Sony PlayStation %ROMPATH%/psx - .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U - .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP - + .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_hw_libretro.dylib %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/pcsx_rearmed_libretro.dylib %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_hw_libretro.dylib %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/pcsx_rearmed_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/duckstation_libretro.dylib %ROM% psx psx @@ -1108,15 +1063,11 @@ snes Nintendo SNES (Super Nintendo) %ROMPATH%/snes - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC - .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP - + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.dylib %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen-s_libretro.dylib %ROM% snes snes @@ -1125,15 +1076,11 @@ snesna Nintendo SNES (Super Nintendo) %ROMPATH%/snesna - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC - .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP - + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x2010_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.dylib %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen-s_libretro.dylib %ROM% snes snesna @@ -1191,9 +1138,7 @@ NEC SuperGrafx %ROMPATH%/supergrafx .pce .PCE .sgx .SGX .cue .CUE .ccd .CCD .chd .CHD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supergrafx_libretro.dylib - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supergrafx_libretro.dylib %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% supergrafx supergrafx @@ -1220,13 +1165,9 @@ tg16 NEC TurboGrafx-16 %ROMPATH%/tg16 - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib %ROM% pcengine tg16 @@ -1234,13 +1175,9 @@ tg-cd NEC TurboGrafx-CD %ROMPATH%/tg-cd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.dylib %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.dylib %ROM% tg-cd diff --git a/resources/systems/unix/es_systems.xml b/resources/systems/unix/es_systems.xml index 105c0669d..fa1976071 100644 --- a/resources/systems/unix/es_systems.xml +++ b/resources/systems/unix/es_systems.xml @@ -15,8 +15,7 @@ Nintendo 64DD %ROMPATH%/64dd .n64 .N64 .v64 .V64 .z64 .Z64 .bin .BIN .u1 .U1 .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mupen64plus_next_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mupen64plus_next_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/parallel_n64_libretro.so %ROM% n64 64dd @@ -101,8 +100,7 @@ .cmd .CMD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbneo_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.so %ROM% @@ -223,15 +221,10 @@ c64 Commodore 64 %ROMPATH%/c64 - .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 - .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ - .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64sc_libretro.so %ROM% - + .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64sc_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x64_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_xscpu64_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_xscpu64_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/vice_x128_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/frodo_libretro.so %ROM% c64 @@ -365,18 +358,10 @@ %ROMPATH%/fba .iso .ISO .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_neogeo_libretro.so - %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.so - %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps2_libretro.so - %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps3_libretro.so - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_neogeo_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps1_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps2_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fbalpha2012_cps3_libretro.so %ROM% arcade fba @@ -472,14 +457,9 @@ genesis Sega Genesis %ROMPATH%/genesis - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx - .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so - %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/blastem_libretro.so %ROM% genesis @@ -544,8 +524,7 @@ Multiple Arcade Machine Emulator %ROMPATH%/mame .cmd .CMD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2003_plus_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2000_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mame_libretro.so %ROM% @@ -576,14 +555,9 @@ mastersystem Sega Master System %ROMPATH%/mastersystem - .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md - .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so - %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/smsplus_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/gearsystem_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.so %ROM% @@ -616,14 +590,9 @@ megadrive Sega Mega Drive %ROMPATH%/megadrive - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx - .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so - %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/genesis_plus_gx_wide_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/picodrive_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/blastem_libretro.so %ROM% megadrive @@ -719,8 +688,7 @@ Nintendo 64 %ROMPATH%/n64 .n64 .N64 .v64 .V64 .z64 .Z64 .bin .BIN .u1 .U1 .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mupen64plus_next_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mupen64plus_next_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/parallel_n64_libretro.so %ROM% n64 n64 @@ -883,12 +851,9 @@ pcengine NEC PC Engine %ROMPATH%/pcengine - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% pcengine pcengine @@ -896,12 +861,9 @@ pcenginecd NEC PC Engine CD %ROMPATH%/pcenginecd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% pcenginecd pcenginecd @@ -981,12 +943,9 @@ psx Sony PlayStation %ROMPATH%/psx - .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U - .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP - + .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_hw_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_psx_hw_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/pcsx_rearmed_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/duckstation_libretro.so %ROM% psx @@ -1110,18 +1069,12 @@ snes Nintendo SNES (Super Nintendo) %ROMPATH%/snes - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC - .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP - + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_libretro.so %ROM% - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.so %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supafaust_libretro.so - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supafaust_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen-s_libretro.so %ROM% snes snes @@ -1130,18 +1083,12 @@ snesna Nintendo SNES (Super Nintendo) %ROMPATH%/snesna - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC - .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP - + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/snes9x2010_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_libretro.so %ROM% - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.so %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supafaust_libretro.so - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/bsnes_mercury_accuracy_libretro.so %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supafaust_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen-s_libretro.so %ROM% snes snesna @@ -1199,9 +1146,7 @@ NEC SuperGrafx %ROMPATH%/supergrafx .pce .PCE .sgx .SGX .cue .CUE .ccd .CCD .chd .CHD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supergrafx_libretro.so - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_supergrafx_libretro.so %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% supergrafx supergrafx @@ -1228,12 +1173,9 @@ tg16 NEC TurboGrafx-16 %ROMPATH%/tg16 - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% pcengine tg16 @@ -1241,12 +1183,9 @@ tg-cd NEC TurboGrafx-CD %ROMPATH%/tg-cd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_libretro.so %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mednafen_pce_fast_libretro.so %ROM% pcenginecd tg-cd diff --git a/resources/systems/windows/es_systems.xml b/resources/systems/windows/es_systems.xml index 8e0b793b8..9dbbea775 100644 --- a/resources/systems/windows/es_systems.xml +++ b/resources/systems/windows/es_systems.xml @@ -15,8 +15,7 @@ Nintendo 64DD %ROMPATH%\64dd .n64 .N64 .v64 .V64 .z64 .Z64 .bin .BIN .u1 .U1 .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mupen64plus_next_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mupen64plus_next_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\parallel_n64_libretro.dll %ROM% n64 64dd @@ -101,8 +100,7 @@ .cmd .CMD .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2000_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbneo_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_libretro.dll %ROM% @@ -223,15 +221,10 @@ c64 Commodore 64 %ROMPATH%\c64 - .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 - .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ - .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_x64sc_libretro.dll %ROM% - + .bin .BIN .cmd .CMD .crt .CRT .d2m .D2M .d4m .D4M .d64 .D64 .d6z .D6Z .d71 .D71 .d7z .D7Z .d80 .D80 .d81 .D81 .d82 .D82 .d8z .D8Z .g41 .G41 .g4z .G4Z .g64 .G64 .g6z .G6Z .gz .GZ .lnx .LNX .m3u .M3U .nbz .NBZ .nib .NIB .p00 .P00 .prg .PRG .t64 .T64 .tap .TAP .vfl .VFL .vsf .VSF .x64 .X64 .x6z .X6Z .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_x64sc_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_x64_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_xscpu64_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_xscpu64_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\vice_x128_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\frodo_libretro.dll %ROM% c64 @@ -365,18 +358,10 @@ %ROMPATH%\fba .iso .ISO .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_neogeo_libretro.dll - %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps1_libretro.dll - %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps2_libretro.dll - %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps3_libretro.dll - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_neogeo_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps1_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps2_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\fbalpha2012_cps3_libretro.dll %ROM% arcade fba @@ -472,14 +457,9 @@ genesis Sega Genesis %ROMPATH%\genesis - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx - .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll - %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\picodrive_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\blastem_libretro.dll %ROM% genesis @@ -544,8 +524,7 @@ Multiple Arcade Machine Emulator %ROMPATH%\mame .cmd .CMD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2003_plus_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2000_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mame_libretro.dll %ROM% @@ -576,14 +555,9 @@ mastersystem Sega Master System %ROMPATH%\mastersystem - .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md - .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll - %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .col .COL .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .rom .ROM .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\smsplus_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\gearsystem_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\picodrive_libretro.dll %ROM% @@ -616,14 +590,9 @@ megadrive Sega Mega Drive %ROMPATH%\megadrive - .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx - .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll - %ROM% - + .68k .68K .bin .BIN .bms .BMS .chd .CHD .cue .CUE .gen .GEN .gg .GG .iso .ISO .m3u .M3U .md .MD .mdx .MDX .sg .SG .sgd .SGD .smd .SMD .sms .SMS .7z .7Z .zip .ZIP + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\genesis_plus_gx_wide_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\picodrive_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\blastem_libretro.dll %ROM% megadrive @@ -719,8 +688,7 @@ Nintendo 64 %ROMPATH%\n64 .n64 .N64 .v64 .V64 .z64 .Z64 .bin .BIN .u1 .U1 .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mupen64plus_next_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mupen64plus_next_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\parallel_n64_libretro.dll %ROM% n64 n64 @@ -883,12 +851,9 @@ pcengine NEC PC Engine %ROMPATH%\pcengine - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% pcengine pcengine @@ -896,12 +861,9 @@ pcenginecd NEC PC Engine CD %ROMPATH%\pcenginecd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% pcenginecd pcenginecd @@ -981,12 +943,9 @@ psx Sony PlayStation %ROMPATH%\psx - .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U - .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP - + .bin .BIN .cbn .CBN .ccd .CCD .chd .CHD .cue .CUE .ecm .ECM .exe .EXE .img .IMG .iso .ISO .m3u .M3U .mdf .MDF .mds .MDS .pbp .PBP .psexe .PSEXE .psf .PSF .toc .TOC .z .Z .znx .ZNX .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_psx_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_psx_hw_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_psx_hw_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\pcsx_rearmed_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\duckstation_libretro.dll %ROM% psx @@ -1110,18 +1069,12 @@ snes Nintendo SNES (Super Nintendo) %ROMPATH%\snes - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC - .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP - + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\snes9x_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\snes9x2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\bsnes_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%\bsnes_mercury_accuracy_libretro.dll %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supafaust_libretro.dll - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\bsnes_mercury_accuracy_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supafaust_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mesen-s_libretro.dll %ROM% snes snes @@ -1130,18 +1083,12 @@ snesna Nintendo SNES (Super Nintendo) %ROMPATH%\snesna - .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC - .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP - + .bin .BIN .bml .BML .bs .BS .bsx .BSX .dx2 .DX2 .fig .FIG .gd3 .GD3 .gd7 .GD7 .mgd .MGD .sfc .SFC .smc .SMC .st .ST .swc .SWC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\snes9x_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\snes9x2010_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\bsnes_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L - %CORE_RETROARCH%\bsnes_mercury_accuracy_libretro.dll %ROM% - - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supafaust_libretro.dll - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\bsnes_mercury_accuracy_libretro.dll %ROM% + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supafaust_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mesen-s_libretro.dll %ROM% snes snesna @@ -1199,9 +1146,7 @@ NEC SuperGrafx %ROMPATH%\supergrafx .pce .PCE .sgx .SGX .cue .CUE .ccd .CCD .chd .CHD .7z .7Z .zip .ZIP - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supergrafx_libretro.dll - %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_supergrafx_libretro.dll %ROM% %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% supergrafx supergrafx @@ -1228,12 +1173,9 @@ tg16 NEC TurboGrafx-16 %ROMPATH%\tg16 - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% pcengine tg16 @@ -1241,12 +1183,9 @@ tg-cd NEC TurboGrafx-CD %ROMPATH%\tg-cd - .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC - .7z .7Z .zip .ZIP - + .bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM% - %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% - + %EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM% pcenginecd tg-cd From 7321bf8f368ae42ff04d3de760728abb75b63ec7 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 27 Sep 2021 21:41:22 +0200 Subject: [PATCH 029/128] Reverted the SVG caching logic. --- es-app/src/views/gamelist/GridGameListView.h | 2 -- es-core/src/components/BadgesComponent.cpp | 12 ++++++------ es-core/src/components/ImageComponent.cpp | 9 ++++----- es-core/src/components/ImageComponent.h | 9 ++------- es-core/src/resources/TextureResource.cpp | 5 ++--- es-core/src/resources/TextureResource.h | 4 +--- 6 files changed, 15 insertions(+), 26 deletions(-) diff --git a/es-app/src/views/gamelist/GridGameListView.h b/es-app/src/views/gamelist/GridGameListView.h index b89852271..2c2f50458 100644 --- a/es-app/src/views/gamelist/GridGameListView.h +++ b/es-app/src/views/gamelist/GridGameListView.h @@ -72,11 +72,9 @@ protected: private: void updateInfoPanel(); - const std::string getImagePath(FileData* file); void initMDLabels(); - void initMDValues(); ImageComponent mMarquee; diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 7ef09ec25..9a28fa3c9 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -30,19 +30,19 @@ BadgesComponent::BadgesComponent(Window* window) mImageComponents = std::map(); ImageComponent mImageFavorite = ImageComponent(window); - mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, true, true); + mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, true); mImageComponents.insert({SLOT_FAVORITE, mImageFavorite}); ImageComponent mImageCompleted = ImageComponent(window); - mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, true, true); + mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, true); mImageComponents.insert({SLOT_COMPLETED, mImageCompleted}); ImageComponent mImageKids = ImageComponent(window); - mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, true, true); + mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, true); mImageComponents.insert({SLOT_KIDS, mImageKids}); ImageComponent mImageBroken = ImageComponent(window); - mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, true, true); + mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, true); mImageComponents.insert({SLOT_BROKEN, mImageBroken}); ImageComponent mImageAltEmu = ImageComponent(window); - mImageAltEmu.setImage(mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR], false, true, true); + mImageAltEmu.setImage(mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR], false, true); mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); } @@ -99,7 +99,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (properties & PATH && elem->has(slot) && mBadgeIcons[slot] != elem->get(slot)) { mBadgeIcons[slot] = elem->get(slot); - mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot], false, true, true); + mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot], false, true); imgChanged = true; } } diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index e3009b888..f6c2d2af2 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -138,7 +138,7 @@ void ImageComponent::resize() onSizeChanged(); } -void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify, bool cacheImage) +void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify) { // Always load bundled graphic resources statically, unless mForceLoad has been set. // This eliminates annoying texture pop-in problems that would otherwise occur. @@ -150,12 +150,11 @@ void ImageComponent::setImage(std::string path, bool tile, bool linearMagnify, b if (mDefaultPath.empty() || !ResourceManager::getInstance()->fileExists(mDefaultPath)) mTexture.reset(); else - mTexture = TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, linearMagnify, - 1.0f, cacheImage); + mTexture = + TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, linearMagnify); } else { - mTexture = - TextureResource::get(path, tile, mForceLoad, mDynamic, linearMagnify, 1.0f, cacheImage); + mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, linearMagnify); } resize(); diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 60ba89cd9..159d65e3b 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -18,20 +18,15 @@ class ImageComponent : public GuiComponent { public: ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true); - virtual ~ImageComponent() {}; + virtual ~ImageComponent() {} void setDefaultImage(std::string path) { mDefaultPath = path; } // Loads the image at the given filepath. Will tile if tile is true (retrieves texture // as tiling, creates vertices accordingly). - void setImage(std::string path, - bool tile = false, - bool linearMagnify = false, - bool cacheSVG = false); - + void setImage(std::string path, bool tile = false, bool linearMagnify = false); // Loads an image from memory. void setImage(const char* data, size_t length, bool tile = false); - // Use an already existing texture. void setImage(const std::shared_ptr& texture, bool resizeTexture = true); diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index 0b5272464..74e5d5bfe 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -148,8 +148,7 @@ std::shared_ptr TextureResource::get(const std::string& path, bool forceLoad, bool dynamic, bool linearMagnify, - float scaleDuringLoad, - bool cacheImage) + float scaleDuringLoad) { std::shared_ptr& rm = ResourceManager::getInstance(); @@ -177,7 +176,7 @@ std::shared_ptr TextureResource::get(const std::string& path, std::shared_ptr data = sTextureDataManager.get(tex.get()); // Is it an SVG? - if (key.first.substr(key.first.size() - 4, std::string::npos) != ".svg" || cacheImage) { + if (key.first.substr(key.first.size() - 4, std::string::npos) != ".svg") { // Probably not. Add it to our map. We don't add SVGs because 2 SVGs might be // rasterized at different sizes. sTextureMap[key] = std::weak_ptr(tex); diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 39d096d6d..223d60fcc 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -30,9 +30,7 @@ public: bool forceLoad = false, bool dynamic = true, bool linearMagnify = false, - float scaleDuringLoad = 1.0f, - bool cacheImage = false); - + float scaleDuringLoad = 1.0f); void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height); virtual void initFromMemory(const char* data, size_t length); static void manualUnload(std::string path, bool tile); From 91f1a0a47daebaab3410f81f8ca3ccc33a3c3f36 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 27 Sep 2021 22:18:19 +0200 Subject: [PATCH 030/128] Improved the speed of the badges code. Also made some other adjustments to the badges and flexbox logic. --- .../views/gamelist/DetailedGameListView.cpp | 2 +- .../src/views/gamelist/VideoGameListView.cpp | 2 +- es-core/src/components/BadgesComponent.cpp | 34 +- es-core/src/components/BadgesComponent.h | 12 +- es-core/src/components/FlexboxComponent.cpp | 5 +- es-core/src/components/FlexboxComponent.h | 6 +- ...badge_altemu.svg => badge_altemulator.svg} | 2 +- resources/graphics/badge_broken.svg | 323 +++++++++++++---- resources/graphics/badge_completed.svg | 325 ++++++++++++++---- resources/graphics/badge_favorite.svg | 317 +++++++++++++---- resources/graphics/badge_kidgame.svg | 320 +++++++++++++---- themes/rbsimple-DE/theme.xml | 5 +- 12 files changed, 1045 insertions(+), 308 deletions(-) rename resources/graphics/{badge_altemu.svg => badge_altemulator.svg} (99%) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 736f1ac76..24c1450a9 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -410,7 +410,7 @@ void DetailedGameListView::updateInfoPanel() ss << (file->metadata.get("completed").compare("true") ? "" : "completed "); ss << (file->metadata.get("kidgame").compare("true") ? "" : "kidgame "); ss << (file->metadata.get("broken").compare("true") ? "" : "broken "); - ss << (file->metadata.get("altemulator").compare("") ? "altemu " : ""); + ss << (file->metadata.get("altemulator").compare("") ? "altemulator " : ""); std::string slots = ss.str(); if (!slots.empty()) slots.pop_back(); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 49b25ddd1..acff97747 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -451,7 +451,7 @@ void VideoGameListView::updateInfoPanel() ss << (file->metadata.get("completed").compare("true") ? "" : "completed "); ss << (file->metadata.get("kidgame").compare("true") ? "" : "kidgame "); ss << (file->metadata.get("broken").compare("true") ? "" : "broken "); - ss << (file->metadata.get("altemulator").compare("") ? "altemu " : ""); + ss << (file->metadata.get("altemulator").compare("") ? "altemulator " : ""); std::string slots = ss.str(); if (!slots.empty()) slots.pop_back(); diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 9a28fa3c9..1d6ebfaab 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -20,30 +20,22 @@ std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETE BadgesComponent::BadgesComponent(Window* window) : FlexboxComponent(window) { - - mBadgeIcons = std::map(); mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.svg"; mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; - mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR] = ":/graphics/badge_altemu.svg"; + mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR] = ":/graphics/badge_altemulator.svg"; - mImageComponents = std::map(); ImageComponent mImageFavorite = ImageComponent(window); - mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, true); mImageComponents.insert({SLOT_FAVORITE, mImageFavorite}); ImageComponent mImageCompleted = ImageComponent(window); - mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, true); mImageComponents.insert({SLOT_COMPLETED, mImageCompleted}); ImageComponent mImageKids = ImageComponent(window); - mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, true); mImageComponents.insert({SLOT_KIDS, mImageKids}); ImageComponent mImageBroken = ImageComponent(window); - mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, true); mImageComponents.insert({SLOT_BROKEN, mImageBroken}); - ImageComponent mImageAltEmu = ImageComponent(window); - mImageAltEmu.setImage(mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR], false, true); - mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmu}); + ImageComponent mImageAltEmulator = ImageComponent(window); + mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmulator}); } BadgesComponent::~BadgesComponent() @@ -94,13 +86,14 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (!elem) return; - bool imgChanged = false; for (auto& slot : mSlots) { - if (properties & PATH && elem->has(slot) && - mBadgeIcons[slot] != elem->get(slot)) { + if (properties & PATH && elem->has(slot)) { mBadgeIcons[slot] = elem->get(slot); - mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot], false, true); - imgChanged = true; + mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]); + } + else { + mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]); + std::string teststring; } } @@ -123,12 +116,5 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, // Apply theme on the flexbox component parent. FlexboxComponent::applyTheme(theme, view, element, properties); - if (imgChanged) - onSizeChanged(); -} - -std::vector BadgesComponent::getHelpPrompts() -{ - std::vector prompts; - return prompts; + onSizeChanged(); } diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index f4584ecf4..e7412233a 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -7,8 +7,8 @@ // Used by gamelist views. // -#ifndef ES_APP_COMPONENTS_BADGES_COMPONENT_H -#define ES_APP_COMPONENTS_BADGES_COMPONENT_H +#ifndef ES_CORE_COMPONENTS_BADGES_COMPONENT_H +#define ES_CORE_COMPONENTS_BADGES_COMPONENT_H #include "FlexboxComponent.h" #include "GuiComponent.h" @@ -20,7 +20,7 @@ #define SLOT_COMPLETED "completed" #define SLOT_KIDS "kidgame" #define SLOT_BROKEN "broken" -#define SLOT_ALTERNATIVE_EMULATOR "altemu" +#define SLOT_ALTERNATIVE_EMULATOR "altemulator" class TextureResource; @@ -30,6 +30,8 @@ public: BadgesComponent(Window* window); ~BadgesComponent(); + static std::shared_ptr& getInstance(); + std::string getValue() const override; // Should be a list of strings. void setValue(const std::string& value) override; @@ -39,12 +41,10 @@ public: const std::string& element, unsigned int properties) override; - virtual std::vector getHelpPrompts() override; - private: static std::vector mSlots; std::map mBadgeIcons; std::map mImageComponents; }; -#endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H +#endif // ES_CORE_COMPONENTS_BADGES_COMPONENT_H diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 9213ae095..ac53174df 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -71,8 +71,8 @@ void FlexboxComponent::computeLayout() float anchorOriginY = 0; // Translation directions when placing items. - glm::vec2 directionLine = {1, 0}; - glm::vec2 directionRow = {0, 1}; + glm::ivec2 directionLine = {1, 0}; + glm::ivec2 directionRow = {0, 1}; // Change direction. if (mDirection == DIRECTION_COLUMN) { @@ -89,6 +89,7 @@ void FlexboxComponent::computeLayout() glm::vec2 newSize = {mItemWidth, oldSize.y * (mItemWidth / oldSize.x)}; i->setSize(newSize); maxItemSize = {std::max(maxItemSize.x, newSize.x), std::max(maxItemSize.y, newSize.y)}; + i->setResize(maxItemSize.x, maxItemSize.y); } // Pre-compute layout parameters. diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index 731a00c8e..d5c1b3a15 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -7,8 +7,8 @@ // Used by gamelist views. // -#ifndef ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H -#define ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H +#ifndef ES_CORE_COMPONENTS_FLEXBOX_COMPONENT_H +#define ES_CORE_COMPONENTS_FLEXBOX_COMPONENT_H #include "GuiComponent.h" #include "renderers/Renderer.h" @@ -69,4 +69,4 @@ private: bool mLayoutValid; }; -#endif // ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H +#endif // ES_CORE_COMPONENTS_FLEXBOX_COMPONENT_H diff --git a/resources/graphics/badge_altemu.svg b/resources/graphics/badge_altemulator.svg similarity index 99% rename from resources/graphics/badge_altemu.svg rename to resources/graphics/badge_altemulator.svg index 777ad8142..943ec1486 100644 --- a/resources/graphics/badge_altemu.svg +++ b/resources/graphics/badge_altemulator.svg @@ -15,7 +15,7 @@ version="1.1" id="svg4842" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="badge_altemu.svg"> + sodipodi:docname="badge_altemulator.svg"> - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + + + image/svg+xml + + + + + + + + + + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg index fca3c4ac5..a4f0a107b 100644 --- a/resources/graphics/badge_completed.svg +++ b/resources/graphics/badge_completed.svg @@ -1,71 +1,260 @@ - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_favorite.svg b/resources/graphics/badge_favorite.svg index 4d5abaeb8..0ccd06479 100644 --- a/resources/graphics/badge_favorite.svg +++ b/resources/graphics/badge_favorite.svg @@ -1,70 +1,253 @@ - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_kidgame.svg b/resources/graphics/badge_kidgame.svg index 13f5a249a..f11131965 100644 --- a/resources/graphics/badge_kidgame.svg +++ b/resources/graphics/badge_kidgame.svg @@ -1,70 +1,256 @@ - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 381379f96..613f51df6 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -243,16 +243,17 @@ based on: 'recalbox-multi' by the Recalbox community row start - 2 + 3 5 5 .035 - favorite completed kidgame broken altemu + favorite completed kidgame broken altemulator :/graphics/badge_favorite.svg :/graphics/badge_completed.svg :/graphics/badge_kidgame.svg :/graphics/badge_broken.svg + :/graphics/badge_altemulator.svg From 33f0b01c55404b55f0a924019aa6229a558df232 Mon Sep 17 00:00:00 2001 From: shadash Date: Sat, 2 Oct 2021 21:29:27 +0200 Subject: [PATCH 031/128] move to avoid unnecessary copies. --- es-core/src/components/FlexboxComponent.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index ac53174df..653fee333 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -9,6 +9,7 @@ #include "components/FlexboxComponent.h" #include +#include #include "Settings.h" #include "ThemeData.h" @@ -31,13 +32,13 @@ FlexboxComponent::FlexboxComponent(Window* window) // Getters/Setters for rendering options. void FlexboxComponent::setDirection(std::string value) { - mDirection = value; + mDirection = std::move(value); mLayoutValid = false; } std::string FlexboxComponent::getDirection() { return mDirection; } void FlexboxComponent::setAlign(std::string value) { - mAlign = value; + mAlign = std::move(value); mLayoutValid = false; } std::string FlexboxComponent::getAlign() { return mAlign; } From f37d9156538534fc3a0d47a396fc2ed89858d776 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Sat, 2 Oct 2021 21:30:10 +0200 Subject: [PATCH 032/128] remove old code --- es-core/src/components/FlexboxComponent.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 653fee333..94ced8bea 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -121,14 +121,6 @@ void FlexboxComponent::computeLayout() float x = anchorX - anchorOriginX * size.x; float y = anchorY - anchorOriginY * size.y; - // Apply item margin. - /*if ((mDirection == "row" && i % std::max(1, (int) mItemsPerLine) != 0) || - (mDirection == "column" && i >= (int) mItemsPerLine)) - x += mItemMargin.x * (directionLine.x >= 0.0f ? 1.0f : -1.0f); - if ((mDirection == "column" && i % std::max(1, (int) mItemsPerLine) != 0) || - (mDirection == "row" && i >= (int) mItemsPerLine)) - y += mItemMargin.y * (directionLine.y >= 0.0f ? 1.0f : -1.0f);*/ - // Apply alignment if (mAlign == ITEM_ALIGN_END) { x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0; From 9d23d124d4bfff3e6d7e6086af06dbb3a10c723e Mon Sep 17 00:00:00 2001 From: shadash Date: Sat, 2 Oct 2021 21:34:38 +0200 Subject: [PATCH 033/128] change casts to c++ style Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 94ced8bea..2827f5995 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -95,7 +95,8 @@ void FlexboxComponent::computeLayout() // Pre-compute layout parameters. int n = mChildren.size(); - int nLines = std::max(1, (int)std::ceil(n / std::max(1, (int)mItemsPerLine))); + int nLines = + std::max(1, static_cast(std::ceil(n / std::max(1, static_cast(mItemsPerLine))))); float lineWidth = (mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); float anchorXStart = anchorX; @@ -144,7 +145,7 @@ void FlexboxComponent::computeLayout() child->setPosition(getPosition().x + x, getPosition().y + y); // Translate anchor. - if ((i + 1) % std::max(1, (int)mItemsPerLine) != 0) { + if ((i + 1) % std::max(1, static_cast(mItemsPerLine)) != 0) { // Translate on same line. anchorX += (size.x + mItemMargin.x) * directionLine.x; anchorY += (size.y + mItemMargin.y) * directionLine.y; From 519644f66cda31bf114609a63cfd6f289f76b3fb Mon Sep 17 00:00:00 2001 From: shadash Date: Sat, 2 Oct 2021 21:58:04 +0200 Subject: [PATCH 034/128] make direction and align an enum. more in line defaults in constructor. getter/setter in header Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.cpp | 65 ++++++--------------- es-core/src/components/FlexboxComponent.h | 63 ++++++++++++-------- 2 files changed, 55 insertions(+), 73 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 2827f5995..64433c099 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -10,7 +10,6 @@ #include "components/FlexboxComponent.h" #include #include - #include "Settings.h" #include "ThemeData.h" #include "resources/TextureResource.h" @@ -20,47 +19,12 @@ FlexboxComponent::FlexboxComponent(Window* window) , mDirection(DEFAULT_DIRECTION) , mAlign(DEFAULT_ALIGN) , mItemsPerLine(DEFAULT_ITEMS_PER_LINE) + , mItemMargin({DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}) , mItemWidth(DEFAULT_ITEM_SIZE_X) + , mLayoutValid(false) { - // Initialize item margins. - mItemMargin = glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}; - - // Layout validity - mLayoutValid = false; } -// Getters/Setters for rendering options. -void FlexboxComponent::setDirection(std::string value) -{ - mDirection = std::move(value); - mLayoutValid = false; -} -std::string FlexboxComponent::getDirection() { return mDirection; } -void FlexboxComponent::setAlign(std::string value) -{ - mAlign = std::move(value); - mLayoutValid = false; -} -std::string FlexboxComponent::getAlign() { return mAlign; } -void FlexboxComponent::setItemsPerLine(unsigned int value) -{ - mItemsPerLine = value; - mLayoutValid = false; -} -unsigned int FlexboxComponent::getItemsPerLine() { return mItemsPerLine; } -void FlexboxComponent::setItemMargin(glm::vec2 value) -{ - mItemMargin = value; - mLayoutValid = false; -} -glm::vec2 FlexboxComponent::getItemMargin() { return mItemMargin; } -void FlexboxComponent::setItemWidth(float value) -{ - mItemWidth = value; - mLayoutValid = false; -} -float FlexboxComponent::getItemWidth() { return mItemWidth; } - void FlexboxComponent::onSizeChanged() { mLayoutValid = false; } void FlexboxComponent::computeLayout() @@ -76,7 +40,7 @@ void FlexboxComponent::computeLayout() glm::ivec2 directionRow = {0, 1}; // Change direction. - if (mDirection == DIRECTION_COLUMN) { + if (mDirection == Direction::row) { directionLine = {0, 1}; directionRow = {1, 0}; } @@ -97,14 +61,14 @@ void FlexboxComponent::computeLayout() int n = mChildren.size(); int nLines = std::max(1, static_cast(std::ceil(n / std::max(1, static_cast(mItemsPerLine))))); - float lineWidth = - (mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); + float lineWidth = (mDirection == Direction::row ? (maxItemSize.y + mItemMargin.y) : + (maxItemSize.x + mItemMargin.x)); float anchorXStart = anchorX; float anchorYStart = anchorY; // Compute total container size. glm::vec2 totalSize = {-mItemMargin.x, -mItemMargin.y}; - if (mDirection == "row") { + if (mDirection == Direction::row) { totalSize.x += (mItemMargin.x + mItemWidth) * mItemsPerLine; totalSize.y += (mItemMargin.y + maxItemSize.y) * nLines; } @@ -123,15 +87,15 @@ void FlexboxComponent::computeLayout() float y = anchorY - anchorOriginY * size.y; // Apply alignment - if (mAlign == ITEM_ALIGN_END) { + if (mAlign == Align::end) { x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) : 0; } - else if (mAlign == ITEM_ALIGN_CENTER) { + else if (mAlign == Align::center) { x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; } - else if (mAlign == ITEM_ALIGN_STRETCH && mDirection == "row") { + else if (mAlign == Align::stretch && mDirection == Direction::row) { child->setSize(child->getSize().x, maxItemSize.y); } @@ -194,10 +158,15 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, return; if (properties & DIRECTION && elem->has("direction")) - mDirection = elem->get("direction"); + mDirection = + elem->get("direction") == "row" ? Direction::row : Direction::column; - if (elem->has("align")) - mAlign = elem->get("align"); + if (elem->has("align")) { + const auto a = elem->get("align"); + mAlign = (a == "start" ? + Align::start : + (a == "end" ? Align::end : (a == "center" ? Align::center : Align::stretch))); + } if (elem->has("itemsPerLine")) mItemsPerLine = elem->get("itemsPerLine"); diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index d5c1b3a15..e52b62360 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -13,40 +13,53 @@ #include "GuiComponent.h" #include "renderers/Renderer.h" -// Definitions for the option values. -#define DIRECTION_ROW "row" -#define DIRECTION_COLUMN "column" -#define ITEM_ALIGN_START "start" -#define ITEM_ALIGN_END "end" -#define ITEM_ALIGN_CENTER "center" -#define ITEM_ALIGN_STRETCH "stretch" - // Default values. -#define DEFAULT_DIRECTION DIRECTION_ROW -#define DEFAULT_ALIGN ITEM_ALIGN_CENTER +#define DEFAULT_DIRECTION Direction::row +#define DEFAULT_ALIGN Align::center #define DEFAULT_ITEMS_PER_LINE 4 #define DEFAULT_MARGIN_X 10.0f #define DEFAULT_MARGIN_Y 10.0f #define DEFAULT_ITEM_SIZE_X 64.0f -class TextureResource; - class FlexboxComponent : public GuiComponent { public: - FlexboxComponent(Window* window); + enum class Direction : char { row, column }; + enum class Align : char { start, end, center, stretch }; + + explicit FlexboxComponent(Window* window); // Getters/Setters for rendering options. - void setDirection(std::string value); - std::string getDirection(); - void setAlign(std::string value); - std::string getAlign(); - void setItemsPerLine(unsigned int value); - unsigned int getItemsPerLine(); - void setItemMargin(glm::vec2 value); - glm::vec2 getItemMargin(); - void setItemWidth(float value); - float getItemWidth(); + [[nodiscard]] Direction getDirection() const { return mDirection; }; + void setDirection(Direction value) + { + mDirection = value; + mLayoutValid = false; + }; + [[nodiscard]] Align getAlign() const { return mAlign; }; + void setAlign(Align value) + { + mAlign = value; + mLayoutValid = false; + }; + [[nodiscard]] unsigned int getItemsPerLine() const { return mItemsPerLine; }; + void setItemsPerLine(unsigned int value) + { + mItemsPerLine = value; + mLayoutValid = false; + }; + [[nodiscard]] glm::vec2 getItemMargin() const { return mItemMargin; }; + void setItemMargin(glm::vec2 value) + { + mItemMargin = value; + mLayoutValid = false; + }; + [[nodiscard]] float getItemWidth() const { return mItemWidth; }; + void setItemWidth(float value) + { + mItemWidth = value; + mLayoutValid = false; + }; void onSizeChanged() override; void render(const glm::mat4& parentTrans) override; @@ -61,8 +74,8 @@ private: void computeLayout(); // Rendering options. - std::string mDirection; - std::string mAlign; + Direction mDirection; + Align mAlign; unsigned int mItemsPerLine; glm::vec2 mItemMargin; float mItemWidth; From dbc9ffb99e24d563960134c8a9347513f1fc4aef Mon Sep 17 00:00:00 2001 From: shadash Date: Sat, 2 Oct 2021 22:00:05 +0200 Subject: [PATCH 035/128] remove unused imports Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 64433c099..e57525e83 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -8,8 +8,6 @@ // #include "components/FlexboxComponent.h" -#include -#include #include "Settings.h" #include "ThemeData.h" #include "resources/TextureResource.h" From 20d14ca71f705db1949d1d05106aa537fc4847a3 Mon Sep 17 00:00:00 2001 From: shadash Date: Sat, 2 Oct 2021 22:00:54 +0200 Subject: [PATCH 036/128] remove redundant virtual keyword Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index e52b62360..6f187f032 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -63,11 +63,11 @@ public: void onSizeChanged() override; void render(const glm::mat4& parentTrans) override; - virtual void applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, - unsigned int properties) override; - virtual std::vector getHelpPrompts() override; + void applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) override; + std::vector getHelpPrompts() override; private: // Calculate flexbox layout. From 95b729dadfdaa9805056dd3994b1d7e522265b31 Mon Sep 17 00:00:00 2001 From: shadash Date: Sat, 2 Oct 2021 22:31:37 +0200 Subject: [PATCH 037/128] replace badges svgs minimize svgs fix bug in flexbox component adjust theme for new badges style Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.cpp | 2 +- resources/graphics/badge_altemulator.svg | 301 ++++++-------------- resources/graphics/badge_broken.svg | 274 +----------------- resources/graphics/badge_completed.svg | 292 ++++--------------- resources/graphics/badge_favorite.svg | 264 +---------------- resources/graphics/badge_kidgame.svg | 266 +---------------- themes/rbsimple-DE/theme.xml | 17 +- 7 files changed, 177 insertions(+), 1239 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index e57525e83..76918b086 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -38,7 +38,7 @@ void FlexboxComponent::computeLayout() glm::ivec2 directionRow = {0, 1}; // Change direction. - if (mDirection == Direction::row) { + if (mDirection == Direction::column) { directionLine = {0, 1}; directionRow = {1, 0}; } diff --git a/resources/graphics/badge_altemulator.svg b/resources/graphics/badge_altemulator.svg index 943ec1486..b5ffc5565 100644 --- a/resources/graphics/badge_altemulator.svg +++ b/resources/graphics/badge_altemulator.svg @@ -1,215 +1,94 @@ - - - - - - - - - - image/svg+xml - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + aria-label="Alternative Emulator " + style="font-size:63.5px;line-height:1.25;font-family:'Nintendo Switch UI';-inkscape-font-specification:'Nintendo Switch UI';stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none"> + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_broken.svg b/resources/graphics/badge_broken.svg index bfb01b538..13f416126 100644 --- a/resources/graphics/badge_broken.svg +++ b/resources/graphics/badge_broken.svg @@ -1,264 +1,12 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg index a4f0a107b..810e52f4b 100644 --- a/resources/graphics/badge_completed.svg +++ b/resources/graphics/badge_completed.svg @@ -1,260 +1,64 @@ - - - - - - - - image/svg+xml - - - - - - - - - - + + + - - + + - - + + - - - - + + aria-label="Completed" + style="font-size:63.5px;line-height:1.25;font-family:'Nintendo Switch UI';-inkscape-font-specification:'Nintendo Switch UI';stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none"> + d="m 293.14147,123.84266 v 5.08 q -7.62,3.73945 -16.15723,3.73945 -12.34723,0 -20.39057,-7.05556 -4.72723,-4.23334 -7.12612,-10.44223 -2.04611,-5.15056 -2.04611,-11.14779 0,-8.466669 4.02167,-15.310563 4.09222,-6.843893 11.14778,-10.371673 6.27945,-3.104447 13.97001,-3.104447 8.7489,0 16.29835,4.586114 l -1.69334,4.162781 q -2.46944,-1.693335 -4.51556,-2.610557 -4.445,-1.975557 -10.08945,-1.975557 -10.795,0 -17.85056,7.05556 -6.70279,6.773338 -6.70279,17.497792 0,11.14778 7.54945,18.20334 6.77334,6.35001 17.6389,6.35001 6.91445,0 12.34723,-2.54 1.48167,-0.77612 3.59834,-2.11667 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> + d="m 320.23469,89.199857 q 8.96056,0 14.67556,6.350004 5.43279,5.997229 5.43279,15.381119 0,9.3839 -5.43279,15.38112 -5.715,6.35001 -14.60501,6.35001 -9.03111,0 -14.74612,-6.35001 -5.43278,-5.99722 -5.43278,-15.66334 0,-6.35 2.82223,-11.500563 2.68111,-4.868337 7.62,-7.54945 4.37445,-2.39889 9.66612,-2.39889 z m 0,3.951113 q -7.62001,0 -12.13557,5.78556 -3.59833,4.58611 -3.59833,12.20612 0,7.83167 4.23334,12.5589 4.51555,5.00944 11.50056,5.00944 7.62,0 12.13556,-5.78556 3.59834,-4.58611 3.59834,-11.99445 0,-8.04334 -4.23334,-12.770562 -4.51556,-5.009448 -11.50056,-5.009448 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> + d="m 379.07805,131.53322 h -4.37445 v -23.70668 q 0,-6.06779 -0.77611,-8.537232 -1.76389,-6.138338 -8.32556,-6.138338 -5.22112,0 -8.60779,4.02167 -2.96333,3.52778 -2.96333,10.23056 v 24.13002 h -4.37445 V 90.328746 h 4.37445 v 5.997226 q 4.09222,-7.126115 12.13556,-7.126115 8.39612,0 11.9239,7.620005 4.79778,-7.620005 13.12334,-7.620005 6.20889,0 9.73667,3.810002 2.89278,3.104447 3.31612,8.607781 0.14111,1.76389 0.14111,5.50334 v 24.41224 h -4.37445 v -23.63613 q 0,-5.99722 -0.70555,-8.537227 -0.84667,-3.245557 -3.6689,-5.009447 -1.97555,-1.199446 -4.79778,-1.199446 -5.29167,0 -8.60778,3.739447 -3.175,3.527783 -3.175,11.077233 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> + d="m 416.33129,90.328746 h 4.37444 v 6.985005 q 5.29167,-8.113894 14.67557,-8.113894 7.97278,0 13.05279,5.573892 5.43278,5.926671 5.43278,16.157231 0,10.16001 -5.43278,16.08668 -5.08001,5.64445 -13.05279,5.64445 -9.45445,0 -14.67557,-8.1139 v 25.7528 h -4.37444 z m 18.55612,2.822224 q -8.25501,0 -11.99445,7.05556 -2.32834,4.30389 -2.32834,10.93612 0,6.20889 2.32834,10.51278 3.73944,7.05556 11.99445,7.05556 6.84389,0 10.86556,-5.00944 3.73945,-4.72723 3.73945,-12.77057 0,-8.04334 -3.73945,-12.770562 -4.02167,-5.009448 -10.86556,-5.009448 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> + d="m 475.38632,127.5821 v 3.95112 h -2.96334 q -4.445,0 -6.35,-1.97556 -1.76389,-1.76389 -1.76389,-6.63223 V 73.677624 h 4.445 v 49.036146 q 0,2.82222 0.635,3.66889 0.84667,1.19944 3.175,1.19944 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> + d="m 513.27462,130.26322 q -5.715,2.39889 -12.41778,2.39889 -9.10168,0 -14.32279,-4.93889 -6.35001,-5.99723 -6.35001,-16.58057 0,-8.89001 4.65667,-15.169456 5.08001,-6.773337 13.8289,-6.773337 6.77334,0 11.50057,4.233336 6.49111,5.785559 6.56167,18.908897 h -32.24391 q 0.35277,7.19668 3.52778,11.07723 4.445,5.29167 13.05278,5.29167 6.27945,0 12.20612,-2.96333 z m -28.71613,-21.80168 h 27.72835 q -0.42333,-4.79778 -2.32833,-8.39612 -3.66889,-6.91445 -11.2889,-6.91445 -8.53723,0 -12.41778,8.25501 -1.41112,3.10444 -1.69334,7.05556 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> + d="M 527.66791,90.328746 V 78.545961 h 4.37444 v 11.782785 h 13.12335 v 3.951114 h -13.12335 v 26.10557 q 0,4.58612 1.05834,6.06778 1.55222,2.25778 6.27945,2.25778 2.46944,0 5.00944,-0.28222 v 3.88056 q -2.89278,0.35278 -5.29167,0.35278 -5.99722,0 -8.53722,-2.32834 -2.11667,-1.905 -2.68112,-5.50333 -0.21166,-1.48167 -0.21166,-4.58612 V 94.27986 h -5.36223 v -3.951114 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> + d="m 583.8301,130.26322 q -5.715,2.39889 -12.41778,2.39889 -9.10168,0 -14.32279,-4.93889 -6.35,-5.99723 -6.35,-16.58057 0,-8.89001 4.65667,-15.169456 5.08,-6.773337 13.82889,-6.773337 6.77334,0 11.50057,4.233336 6.49111,5.785559 6.56167,18.908897 h -32.24391 q 0.35278,7.19668 3.52778,11.07723 4.445,5.29167 13.05278,5.29167 6.27945,0 12.20612,-2.96333 z m -28.71613,-21.80168 h 27.72835 q -0.42333,-4.79778 -2.32833,-8.39612 -3.66889,-6.91445 -11.2889,-6.91445 -8.53722,0 -12.41778,8.25501 -1.41111,3.10444 -1.69334,7.05556 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> - - - - - - - - - - - - - - - - + d="m 627.50393,124.54821 q -5.22111,8.1139 -14.67556,8.1139 -7.97279,0 -13.05279,-5.64445 -5.43278,-5.92667 -5.43278,-16.08668 0,-10.16 5.43278,-16.086675 5.08,-5.644448 13.05279,-5.644448 9.38389,0 14.67556,8.113894 V 73.677624 h 4.37445 v 57.855596 h -4.37445 z M 613.32226,93.15097 q -6.77334,0 -10.86557,5.009448 -3.73944,4.656672 -3.73944,12.770562 0,8.04334 3.73944,12.77057 3.95112,5.00944 10.86557,5.00944 8.255,0 11.99445,-7.05556 2.32833,-4.30389 2.32833,-10.93611 0,-6.27945 -2.32833,-10.51279 -3.88056,-7.05556 -11.99445,-7.05556 z" + style="font-size:70.5556px;fill:#222222;fill-opacity:1;stroke:#222222;stroke-width:2.11667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/> + + - - - - - - - - diff --git a/resources/graphics/badge_favorite.svg b/resources/graphics/badge_favorite.svg index 0ccd06479..7bc680e93 100644 --- a/resources/graphics/badge_favorite.svg +++ b/resources/graphics/badge_favorite.svg @@ -1,253 +1,13 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/resources/graphics/badge_kidgame.svg b/resources/graphics/badge_kidgame.svg index f11131965..eee4a1266 100644 --- a/resources/graphics/badge_kidgame.svg +++ b/resources/graphics/badge_kidgame.svg @@ -1,256 +1,12 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 613f51df6..45c6ae52b 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -239,21 +239,12 @@ based on: 'recalbox-multi' by the Recalbox community 0.8125 0.65 0 0 - - - row + column start - 3 - 5 5 - .035 - - + 4 + 25 5 + .059 favorite completed kidgame broken altemulator - :/graphics/badge_favorite.svg - :/graphics/badge_completed.svg - :/graphics/badge_kidgame.svg - :/graphics/badge_broken.svg - :/graphics/badge_altemulator.svg From 714be4b52aa5cb2a03889675450762ec8c07e0dc Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 5 Oct 2021 17:57:33 +0200 Subject: [PATCH 038/128] Replaced default badges with colorized rectangular design. --- resources/graphics/badge_altemulator.svg | 524 +++++++++++++++++++---- resources/graphics/badge_broken.svg | 256 ++++++++++- resources/graphics/badge_completed.svg | 309 ++++++++++--- resources/graphics/badge_favorite.svg | 260 ++++++++++- resources/graphics/badge_kidgame.svg | 262 +++++++++++- 5 files changed, 1426 insertions(+), 185 deletions(-) diff --git a/resources/graphics/badge_altemulator.svg b/resources/graphics/badge_altemulator.svg index b5ffc5565..bf04c7319 100644 --- a/resources/graphics/badge_altemulator.svg +++ b/resources/graphics/badge_altemulator.svg @@ -1,94 +1,438 @@ - - - - - - - - - - - + + + + + + + + + + image/svg+xml + + + + + + + - - - - - - - - - - - - - - - - - - - + id="g16" + transform="matrix(0.10079384,0,0,0.10062218,-2.2563844e-4,266.11692)" + style="fill:#d7d7d7;fill-opacity:1"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - diff --git a/resources/graphics/badge_broken.svg b/resources/graphics/badge_broken.svg index 13f416126..a1d7dbc3c 100644 --- a/resources/graphics/badge_broken.svg +++ b/resources/graphics/badge_broken.svg @@ -1,12 +1,246 @@ - - - - - + + + + + + + + + + image/svg+xml + + + + + + + + + - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg index 810e52f4b..a09e55f4c 100644 --- a/resources/graphics/badge_completed.svg +++ b/resources/graphics/badge_completed.svg @@ -1,64 +1,255 @@ - - - + + + + + + + + image/svg+xml + + + + + + + + - - + + - - - - - - - - - - - - - - - - + + + inkscape:connector-curvature="0" + id="path28-8" + d="m 17.401,237.59 c 0,6.2332 2.6179,7.5296 6.8066,7.5296 1.0222,0 1.845,-0.0748 2.8672,-0.27426 l -0.52358,-3.9394 -2.2938,0.17453 c -1.0222,0.0499 -1.6705,-0.52358 -1.6705,-3.4906 0,-2.96702 0.74798,-3.5155 1.6705,-3.4656 l 2.2938,0.14959 0.54852,-3.9144 c -1.0222,-0.22439 -1.8201,-0.42385 -2.8423,-0.42385 -4.2136,0 -6.8565,1.7952 -6.8565,7.6543 z" + style="fill:#f0f0f0;fill-opacity:1" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_favorite.svg b/resources/graphics/badge_favorite.svg index 7bc680e93..4b8236cae 100644 --- a/resources/graphics/badge_favorite.svg +++ b/resources/graphics/badge_favorite.svg @@ -1,13 +1,247 @@ - - - - - - - - - - \ No newline at end of file + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_kidgame.svg b/resources/graphics/badge_kidgame.svg index eee4a1266..565806a74 100644 --- a/resources/graphics/badge_kidgame.svg +++ b/resources/graphics/badge_kidgame.svg @@ -1,12 +1,250 @@ - - - - - - - - - - \ No newline at end of file + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3ff5f90f364d0b3d0385f30f905378bd57ca1990 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 5 Oct 2021 17:59:44 +0200 Subject: [PATCH 039/128] Fixed a sizing issue in FlexboxComponent. --- es-core/src/components/FlexboxComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 76918b086..2e1262a07 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -173,7 +173,7 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, mItemMargin = elem->get("itemMargin"); if (elem->has("itemWidth")) - mItemWidth = elem->get("itemWidth") * scale.x; + mItemWidth = floorf(elem->get("itemWidth") * scale.x); GuiComponent::applyTheme(theme, view, element, properties); From fb2f254d7e5c49e8deda4abbfa86c2ec30f9b573 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 5 Oct 2021 18:12:24 +0200 Subject: [PATCH 040/128] (rbsimple-DE) Some adjustments to the badges placement and sizing. --- themes/rbsimple-DE/theme.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 45c6ae52b..fef0db606 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -237,13 +237,13 @@ based on: 'recalbox-multi' by the Recalbox community right - 0.8125 0.65 + 0.8125 0.675 0 0 - column + row start - 4 - 25 5 - .059 + 3 + 20 20 + .038 favorite completed kidgame broken altemulator From 272584ac3934b11e967bd98adc593f8782572042 Mon Sep 17 00:00:00 2001 From: shadash Date: Fri, 8 Oct 2021 20:37:24 +0200 Subject: [PATCH 041/128] change font in alt emu badge Signed-off-by: Sophia Hadash --- resources/graphics/badge_altemulator.svg | 413 +++++++++++------------ 1 file changed, 203 insertions(+), 210 deletions(-) diff --git a/resources/graphics/badge_altemulator.svg b/resources/graphics/badge_altemulator.svg index bf04c7319..0a54263ac 100644 --- a/resources/graphics/badge_altemulator.svg +++ b/resources/graphics/badge_altemulator.svg @@ -1,49 +1,48 @@ - - + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="80" + height="112" + viewBox="0 0 21.166666 29.633334" + version="1.1" + id="svg4842" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + sodipodi:docname="badge_altemulator.svg"> + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7.0546875" + inkscape:cx="29.041584" + inkscape:cy="62.825107" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="3440" + inkscape:window-height="1355" + inkscape:window-x="2560" + inkscape:window-y="0" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + units="px" + inkscape:snap-nodes="false" + inkscape:snap-others="false" + inkscape:snap-global="true" + inkscape:document-rotation="0"/> @@ -62,47 +61,47 @@ id="layer1" transform="translate(-1.9829021e-4,-266.11715)"> + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56896;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + id="rect5286" + width="21.166666" + height="29.633333" + x="0.0001982902" + y="266.11713" /> + x="3.0257001" + y="2.9029" + width="204.36" + height="288.81" + id="rect14" + style="fill:#d7d7d7;fill-opacity:1;stroke-width:0.26458"/> + x="7.7007999" + y="7.7174001" + width="194.89999" + height="279.22" + id="rect18" + style="stroke-width:0.26458"/> + style="fill:#e6e6e6;fill-opacity:1;stroke-width:0.0266453" + id="rect22" + height="17.395565" + width="17.540144" + y="271.47574" + x="1.8325089" /> + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.25971" + id="g56-4" + aria-label="CONTENT RATED BY" + transform="matrix(0.09632353,0,0,0.10097609,0.27971034,266.32293)"> + d="m 2.2271284,292.95117 c 0,0.40604 0.015331,0.79582 0.030663,1.17749 0,0.0243 0.030663,0.0406 0.076656,0.0406 0.2529687,0.008 1.364477,0.0203 1.625048,0.0203 0.2529687,0 1.073192,-0.004 1.3184891,-0.0122 v -0.29642 -0.35327 c -0.00767,-0.0203 -0.038327,-0.0406 -0.076656,-0.0406 -0.2606263,-0.004 -1.096179,-0.008 -1.3568192,-0.008 h -0.099653 v -0.21114 h 0.9965315 v -0.268 -0.30859 c -0.00767,-0.0203 -0.038327,-0.0406 -0.076656,-0.0406 -0.1993091,0 -0.6132565,-0.004 -0.919871,-0.004 v -0.21521 h 0.2989565 c 0.2529687,0 0.9735306,0 1.2188276,-0.008 v -0.29642 -0.35326 c -0.00767,-0.0203 -0.038327,-0.0406 -0.076656,-0.0406 -0.2606264,-0.004 -1.0808497,-0.008 -1.3414899,-0.008 -0.2529687,0 -1.3491477,0.004 -1.5867594,0.008 -0.022997,0.40604 -0.030663,0.81206 -0.030663,1.21812 z" + id="path78-4" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.036704"/> + d="m 5.6153964,294.14527 c 0.5289244,0.0325 1.0961791,0.0446 1.6174179,0.0446 1.0348618,0 1.8780861,-0.15023 1.8780861,-0.79582 0,-0.5644 -0.9505434,-0.71463 -1.5867594,-0.81618 -0.3142859,-0.0487 -0.5519252,-0.0853 -0.5519252,-0.15834 0,-0.0731 0.1533072,-0.1137 0.3909464,-0.1137 0.4752649,0 1.0195325,0.0812 1.3798065,0.16647 l 0.1763081,-0.67809 c -0.5135951,-0.0527 -1.0348617,-0.0894 -1.4795233,-0.0894 -1.0501911,0 -1.8167688,0.0487 -1.8167688,0.70652 0,0.62531 0.8508821,0.75525 1.4258361,0.84049 0.2989567,0.0446 0.5212666,0.0772 0.5212666,0.15836 0,0.065 -0.1226499,0.14617 -0.4139474,0.14617 -0.3449586,0 -0.8202234,-0.0528 -1.3568193,-0.14212 z" + id="path80-9" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.036704"/> + d="m 9.4307297,292.9475 c 0,0.10149 0.00613,0.19489 0.01226,0.29641 0,0.0243 0.024523,0.0406 0.061303,0.0406 0.2023048,0.008 0.9931183,0.0203 1.2015133,0.0203 0.202306,0 0.919554,-0.004 1.054426,-0.0122 0,-0.0853 0.0061,-0.17459 0.0061,-0.2558 0,-0.20302 -0.0061,-0.24769 -0.0061,-0.34514 -0.0061,-0.0203 -0.03065,-0.0406 -0.06131,-0.0406 -0.208428,-0.004 -0.692729,-0.008 -0.901169,-0.008 -0.202303,0 -1.1647903,0.004 -1.354836,0.008 -0.00613,0.13399 -0.01226,0.21926 -0.01226,0.29642 z" + id="path82-1" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.0328234"/> + d="m 13.651876,292.95117 c 0,-0.19489 0,-0.36544 0.0077,-0.53598 h 0.130314 c 0.206967,0 0.360288,0.0528 0.360288,0.53598 0,0.48319 -0.153307,0.54004 -0.360288,0.54004 h -0.130314 c -0.0077,-0.17054 -0.0077,-0.34108 -0.0077,-0.54004 z m -1.517812,0 c 0,0.40604 0.01533,0.75525 0.01533,1.13692 0,0.0243 0.03066,0.0406 0.07666,0.0406 0.528925,0.0284 1.011861,0.0487 1.433466,0.0487 1.28783,0 2.092697,-0.21115 2.092697,-1.22628 0,-0.95418 -0.812553,-1.24657 -2.108095,-1.24657 -0.429277,0 -0.98886,0.0284 -1.494784,0.0691 0,0.40604 -0.01533,0.7715 -0.01533,1.17749 z" + id="path84-7" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.036704"/> + d="m 15.999759,292.92458 c 0,0.3953 0.01492,0.77476 0.02985,1.14634 0,0.0237 0.02985,0.0395 0.07463,0.0395 0.246278,0.008 1.328384,0.0198 1.582062,0.0198 0.246277,0 1.044804,-0.004 1.283611,-0.0118 v -0.28858 -0.34391 c -0.0075,-0.0198 -0.03731,-0.0395 -0.07463,-0.0395 -0.253731,-0.004 -1.067182,-0.008 -1.320928,-0.008 h -0.09702 v -0.20557 h 0.970171 v -0.2609 -0.30043 c -0.0075,-0.0198 -0.03731,-0.0395 -0.07463,-0.0395 -0.194037,0 -0.597034,-0.004 -0.895539,-0.004 v -0.20951 h 0.291049 c 0.246277,0 0.947778,0 1.186587,-0.008 v -0.28857 -0.34391 c -0.0075,-0.0198 -0.03731,-0.0395 -0.07463,-0.0395 -0.253731,-0.004 -1.052258,-0.008 -1.306003,-0.008 -0.246277,0 -1.313459,0.004 -1.544785,0.008 -0.02239,0.39529 -0.02985,0.79057 -0.02985,1.18589 z" + id="path86-0" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.0357331"/> + id="g54" + aria-label="CONTENT RATED BY" + transform="matrix(0.09871346,0,0,0.10292059,-29.557342,270.26518)" + style="fill:#ffffff;stroke-width:0.25912"> + id="path26" + d="m 17.44,237.05 c 0,6.2189 2.6119,7.5125 6.7911,7.5125 1.0199,0 1.8408,-0.0746 2.8607,-0.27364 l -0.52239,-3.9304 -2.2886,0.17413 c -1.0199,0.0497 -1.6667,-0.52239 -1.6667,-3.4826 0,-2.9602 0.74627,-3.5075 1.6667,-3.4577 l 2.2886,0.14926 0.54726,-3.9055 c -1.0199,-0.22388 -1.8159,-0.42289 -2.8358,-0.42289 -4.204,0 -6.8408,1.791 -6.8408,7.6368 z" + inkscape:connector-curvature="0"/> + id="path28" + d="m 27.505,237.05 c 0,5.7712 2.1642,7.612 6.9154,7.612 3.5075,-0.17413 5.7214,-2.7114 5.7214,-7.612 0,-5.7463 -2.2388,-7.6368 -6.9652,-7.6368 -3.5075,0.17413 -5.6717,2.7612 -5.6717,7.6368 z m 5.1741,0 c 0,-2.9602 0.42289,-3.2836 1.0945,-3.2836 0.67161,0 1.1692,0.32339 1.1692,3.2836 0,2.9602 -0.42289,3.2836 -1.0945,3.2836 -0.67161,0 -1.1692,-0.32339 -1.1692,-3.2836 z" + inkscape:connector-curvature="0"/> - - - - - - - - + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Dyuthi;-inkscape-font-specification:Dyuthi;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="19.372959" + y="271.49698" + id="text5145"/> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 11.142864,276.8821 c 0,-0.13102 -0.08756,-0.2366 -0.19606,-0.2366 h -0.245083 v -0.8903 h -0.444919 v 0.8903 h -0.245074 c -0.1085141,0 -0.1960697,0.10558 -0.1960697,0.2366 v 0.80877 c 0,0.13102 0.087556,0.23659 0.1960697,0.23659 h 0.935076 c 0.108505,0 0.19606,-0.10557 0.19606,-0.23659 z m -0.437842,0.17863 v 0.45151 h -0.451513 v -0.45151 z" + id="rect5069-0-5-6-6" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 5.8349883,276.8821 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 5.3938449 v -0.8903 H 4.9489256 v 0.8903 H 4.703852 c -0.1085142,0 -0.1960698,0.10558 -0.1960698,0.2366 v 0.80877 c 0,0.13102 0.087556,0.23659 0.1960698,0.23659 h 0.9350756 c 0.1085051,0 0.1960607,-0.10557 0.1960607,-0.23659 z m -0.4378418,0.17863 v 0.45151 H 4.9456332 v -0.45151 z" + id="rect5069-0-5-1-0-8-8" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 7.605691,276.8821 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 7.1645475 v -0.8903 H 6.7196374 v 0.8903 H 6.4745547 c -0.1085143,0 -0.1960608,0.10558 -0.1960608,0.2366 v 0.80877 c 0,0.13102 0.087556,0.23659 0.1960608,0.23659 h 0.9350756 c 0.1085051,0 0.1960607,-0.10557 0.1960607,-0.23659 z m -0.4378419,0.17863 v 0.45151 H 6.7163358 v -0.45151 z" + id="rect5069-0-5-1-0-4" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 9.3740406,276.8821 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 8.9328972 v -0.8903 H 8.487987 v 0.8903 H 8.2429043 c -0.1085143,0 -0.1960607,0.10558 -0.1960607,0.2366 v 0.80877 c 0,0.13102 0.087556,0.23659 0.1960607,0.23659 h 0.9350756 c 0.1085051,0 0.1960607,-0.10557 0.1960607,-0.23659 z m -0.4378419,0.17863 v 0.45151 H 8.4846854 v -0.45151 z" + id="rect5069-0-5-1-7" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 12.916869,276.8821 c 0,-0.13102 -0.08756,-0.2366 -0.196061,-0.2366 h -0.245083 v -0.8903 h -0.444919 v 0.8903 h -0.245074 c -0.108514,0 -0.19607,0.10558 -0.19607,0.2366 v 0.80877 c 0,0.13102 0.08756,0.23659 0.19607,0.23659 h 0.935076 c 0.108505,0 0.196061,-0.10557 0.196061,-0.23659 z m -0.437842,0.17863 v 0.45151 h -0.451514 v -0.45151 z" + id="rect5069-0-5-2" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 14.686158,276.8821 c 0,-0.13102 -0.08756,-0.2366 -0.196061,-0.2366 h -0.245083 v -0.8903 h -0.44491 v 0.8903 h -0.245083 c -0.108514,0 -0.196061,0.10558 -0.196061,0.2366 v 0.80877 c 0,0.13102 0.08756,0.23659 0.196061,0.23659 h 0.935076 c 0.108505,0 0.196061,-0.10557 0.196061,-0.23659 z m -0.437842,0.17863 v 0.45151 h -0.451514 v -0.45151 z" + id="rect5069-0-4" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 11.142864,282.65618 c 0,-0.13102 -0.08756,-0.2366 -0.19606,-0.2366 h -0.935076 c -0.1084776,0 -0.1960697,0.10558 -0.1960697,0.2366 v 0.80876 c 0,0.13102 0.087556,0.2366 0.1960697,0.2366 h 0.245074 v 0.8903 h 0.444919 v -0.8903 h 0.245083 c 0.108477,0 0.19606,-0.10558 0.19606,-0.2366 z m -0.437842,0.17862 v 0.45152 h -0.451513 v -0.45152 z" + id="rect5069-0-5-6" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 16.452628,282.65618 c 0,-0.13102 -0.08755,-0.2366 -0.19607,-0.2366 h -0.935075 c -0.108478,0 -0.196061,0.10558 -0.196061,0.2366 v 0.80876 c 0,0.13102 0.08756,0.2366 0.196061,0.2366 h 0.245083 v 0.8903 h 0.444919 v -0.8903 h 0.245073 c 0.108478,0 0.19607,-0.10558 0.19607,-0.2366 z m -0.437851,0.17862 v 0.45152 h -0.451513 v -0.45152 z" + id="rect5195" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 5.8349883,282.65618 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 4.703852 c -0.1084777,0 -0.1960698,0.10558 -0.1960698,0.2366 v 0.80876 c 0,0.13102 0.087556,0.2366 0.1960698,0.2366 h 0.2450736 v 0.8903 h 0.4449193 v -0.8903 h 0.2450827 c 0.1084778,0 0.1960607,-0.10558 0.1960607,-0.2366 z m -0.4378418,0.17862 v 0.45152 H 4.9456332 v -0.45152 z" + id="rect5069-0-5-1-0-8" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 7.605691,282.65618 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 6.4745547 c -0.1084778,0 -0.1960608,0.10558 -0.1960608,0.2366 v 0.80876 c 0,0.13102 0.087556,0.2366 0.1960608,0.2366 h 0.2450827 v 0.8903 h 0.4449101 v -0.8903 h 0.2450828 c 0.1084777,0 0.1960607,-0.10558 0.1960607,-0.2366 z m -0.4378419,0.17862 v 0.45152 H 6.7163358 v -0.45152 z" + id="rect5069-0-5-1-0" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 9.3740406,282.65618 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 8.2429043 c -0.1084778,0 -0.1960607,0.10558 -0.1960607,0.2366 v 0.80876 c 0,0.13102 0.087556,0.2366 0.1960607,0.2366 H 8.487987 v 0.8903 h 0.4449102 v -0.8903 h 0.2450827 c 0.1084777,0 0.1960607,-0.10558 0.1960607,-0.2366 z m -0.4378419,0.17862 v 0.45152 H 8.4846854 v -0.45152 z" + id="rect5069-0-5-1" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 12.916869,282.65618 c 0,-0.13102 -0.08756,-0.2366 -0.196061,-0.2366 h -0.935076 c -0.108478,0 -0.19607,0.10558 -0.19607,0.2366 v 0.80876 c 0,0.13102 0.08756,0.2366 0.19607,0.2366 h 0.245074 v 0.8903 h 0.444919 v -0.8903 h 0.245083 c 0.108478,0 0.196061,-0.10558 0.196061,-0.2366 z m -0.437842,0.17862 v 0.45152 h -0.451514 v -0.45152 z" + id="rect5069-0-5" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 14.686158,282.65618 c 0,-0.13102 -0.08756,-0.2366 -0.196061,-0.2366 h -0.935076 c -0.108478,0 -0.196061,0.10558 -0.196061,0.2366 v 0.80876 c 0,0.13102 0.08756,0.2366 0.196061,0.2366 h 0.245083 v 0.8903 h 0.44491 v -0.8903 h 0.245083 c 0.108478,0 0.196061,-0.10558 0.196061,-0.2366 z m -0.437842,0.17862 v 0.45152 h -0.451514 v -0.45152 z" + id="rect5069-0" + inkscape:connector-curvature="0" /> + id="rect5061" + d="m 4.7486749,277.07833 c -0.3682172,0 -0.664559,0.25094 -0.664559,0.56276 v 5.0648 c 0,0.31182 0.2963418,0.56276 0.664559,0.56276 H 16.456487 c 0.368217,0 0.664559,-0.25094 0.664559,-0.56276 v -1.64279 a 0.91714642,0.91714642 0 0 1 -0.698149,-0.88987 0.91714642,0.91714642 0 0 1 0.698149,-0.88935 v -1.64279 c 0,-0.31182 -0.296342,-0.56276 -0.664559,-0.56276 z" + style="opacity:1;fill:#fcfcfd;fill-opacity:1;stroke:#282828;stroke-width:0.274902;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + inkscape:connector-curvature="0"/> + rx="0.42198506" + ry="0.42198506" + y="277.72012" + x="7.7069221" + height="4.9067254" + width="5.7913175" + id="rect5019" + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.213968;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"/> + + + + + + + + + + From 123e50cf17cc8c3658ad6fa5260757aa7d9fd47e Mon Sep 17 00:00:00 2001 From: shadash Date: Sat, 9 Oct 2021 17:04:04 +0200 Subject: [PATCH 042/128] change dimension constraints to use container size. make margins proportional to screen size. Signed-off-by: Sophia Hadash --- es-core/src/ThemeData.cpp | 3 +- es-core/src/components/FlexboxComponent.cpp | 53 ++++++++++----------- es-core/src/components/FlexboxComponent.h | 22 ++++----- themes/rbsimple-DE/theme.xml | 5 +- 4 files changed, 38 insertions(+), 45 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 8dd5ae90c..0b35a30e3 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -148,12 +148,13 @@ std::map> The {"zIndex", FLOAT}}}, {"badges", {{"pos", NORMALIZED_PAIR}, + {"size", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, {"direction", STRING}, {"align", STRING}, {"itemsPerLine", FLOAT}, + {"lines", FLOAT}, {"itemMargin", NORMALIZED_PAIR}, - {"itemWidth", FLOAT}, {"slots", STRING}, {"customBadgeIcon", PATH}, {"visible", BOOLEAN}, diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 2e1262a07..57d8d649c 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -17,8 +17,8 @@ FlexboxComponent::FlexboxComponent(Window* window) , mDirection(DEFAULT_DIRECTION) , mAlign(DEFAULT_ALIGN) , mItemsPerLine(DEFAULT_ITEMS_PER_LINE) + , mLines(DEFAULT_LINES) , mItemMargin({DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}) - , mItemWidth(DEFAULT_ITEM_SIZE_X) , mLayoutValid(false) { } @@ -43,40 +43,37 @@ void FlexboxComponent::computeLayout() directionRow = {1, 0}; } - // Set children sizes. - glm::vec2 maxItemSize = {0.0f, 0.0f}; + // Compute children maximal dimensions. + // direction == row + // maxItemSize = { ((mMaxSize.x - mItemMargin.x) / mItemsPerLine) - mItemMargin.x, ((mMaxSize.y + // - mItemMargin.y) / mLines) - mItemMargin.y}; + glm::vec2 grid; + if (mDirection == Direction::row) + grid = {mItemsPerLine, mLines}; + else + grid = {mLines, mItemsPerLine}; + glm::vec2 maxItemSize = ((mSize - mItemMargin) / grid) - mItemMargin; + + // Set final children dimensions. for (auto i : mChildren) { auto oldSize = i->getSize(); if (oldSize.x == 0) - oldSize.x = DEFAULT_ITEM_SIZE_X; - glm::vec2 newSize = {mItemWidth, oldSize.y * (mItemWidth / oldSize.x)}; + oldSize.x = maxItemSize.x; + glm::vec2 sizeMaxX = {maxItemSize.x, oldSize.y * (maxItemSize.x / oldSize.x)}; + glm::vec2 sizeMaxY = {oldSize.x * (maxItemSize.y / oldSize.y), maxItemSize.y}; + glm::vec2 newSize = + sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; i->setSize(newSize); - maxItemSize = {std::max(maxItemSize.x, newSize.x), std::max(maxItemSize.y, newSize.y)}; - i->setResize(maxItemSize.x, maxItemSize.y); } // Pre-compute layout parameters. - int n = mChildren.size(); - int nLines = - std::max(1, static_cast(std::ceil(n / std::max(1, static_cast(mItemsPerLine))))); float lineWidth = (mDirection == Direction::row ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); float anchorXStart = anchorX; float anchorYStart = anchorY; - // Compute total container size. - glm::vec2 totalSize = {-mItemMargin.x, -mItemMargin.y}; - if (mDirection == Direction::row) { - totalSize.x += (mItemMargin.x + mItemWidth) * mItemsPerLine; - totalSize.y += (mItemMargin.y + maxItemSize.y) * nLines; - } - else { - totalSize.x += (mItemMargin.x + mItemWidth) * nLines; - totalSize.y += (mItemMargin.y + maxItemSize.y) * mItemsPerLine; - } - // Iterate through the children. - for (int i = 0; i < n; i++) { + for (int i = 0; i < static_cast(mChildren.size()); i++) { GuiComponent* child = mChildren[i]; auto size = child->getSize(); @@ -99,9 +96,9 @@ void FlexboxComponent::computeLayout() // Apply origin. if (mOrigin.x > 0 && mOrigin.x <= 1) - x -= mOrigin.x * totalSize.x; + x -= mOrigin.x * mSize.x; if (mOrigin.y > 0 && mOrigin.y <= 1) - y -= mOrigin.y * totalSize.y; + y -= mOrigin.y * mSize.y; // Store final item position. child->setPosition(getPosition().x + x, getPosition().y + y); @@ -169,11 +166,11 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("itemsPerLine")) mItemsPerLine = elem->get("itemsPerLine"); - if (elem->has("itemMargin")) - mItemMargin = elem->get("itemMargin"); + if (elem->has("lines")) + mLines = elem->get("lines"); - if (elem->has("itemWidth")) - mItemWidth = floorf(elem->get("itemWidth") * scale.x); + if (elem->has("itemMargin")) + mItemMargin = elem->get("itemMargin") * scale; GuiComponent::applyTheme(theme, view, element, properties); diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index 6f187f032..ad7ee90db 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -17,9 +17,9 @@ #define DEFAULT_DIRECTION Direction::row #define DEFAULT_ALIGN Align::center #define DEFAULT_ITEMS_PER_LINE 4 +#define DEFAULT_LINES 1 #define DEFAULT_MARGIN_X 10.0f #define DEFAULT_MARGIN_Y 10.0f -#define DEFAULT_ITEM_SIZE_X 64.0f class FlexboxComponent : public GuiComponent { @@ -30,12 +30,6 @@ public: explicit FlexboxComponent(Window* window); // Getters/Setters for rendering options. - [[nodiscard]] Direction getDirection() const { return mDirection; }; - void setDirection(Direction value) - { - mDirection = value; - mLayoutValid = false; - }; [[nodiscard]] Align getAlign() const { return mAlign; }; void setAlign(Align value) { @@ -48,18 +42,18 @@ public: mItemsPerLine = value; mLayoutValid = false; }; + [[nodiscard]] unsigned int getLines() const { return mLines; }; + void setLines(unsigned int value) + { + mLines = value; + mLayoutValid = false; + }; [[nodiscard]] glm::vec2 getItemMargin() const { return mItemMargin; }; void setItemMargin(glm::vec2 value) { mItemMargin = value; mLayoutValid = false; }; - [[nodiscard]] float getItemWidth() const { return mItemWidth; }; - void setItemWidth(float value) - { - mItemWidth = value; - mLayoutValid = false; - }; void onSizeChanged() override; void render(const glm::mat4& parentTrans) override; @@ -77,8 +71,8 @@ private: Direction mDirection; Align mAlign; unsigned int mItemsPerLine; + unsigned int mLines; glm::vec2 mItemMargin; - float mItemWidth; bool mLayoutValid; }; diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index fef0db606..ecf21ecde 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -238,12 +238,13 @@ based on: 'recalbox-multi' by the Recalbox community 0.8125 0.675 + 0.1 0.2 0 0 row start 3 - 20 20 - .038 + 2 + 0.005 0.005 favorite completed kidgame broken altemulator From a93b975ca01374e0c8beda9ba228d56c9f5721b9 Mon Sep 17 00:00:00 2001 From: shadash Date: Sun, 10 Oct 2021 13:29:26 +0200 Subject: [PATCH 043/128] bug fixes, adjust theme badge proportions Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.cpp | 22 +++++++++++---------- themes/rbsimple-DE/theme.xml | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 57d8d649c..df65694da 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -44,15 +44,12 @@ void FlexboxComponent::computeLayout() } // Compute children maximal dimensions. - // direction == row - // maxItemSize = { ((mMaxSize.x - mItemMargin.x) / mItemsPerLine) - mItemMargin.x, ((mMaxSize.y - // - mItemMargin.y) / mLines) - mItemMargin.y}; glm::vec2 grid; if (mDirection == Direction::row) grid = {mItemsPerLine, mLines}; else grid = {mLines, mItemsPerLine}; - glm::vec2 maxItemSize = ((mSize - mItemMargin) / grid) - mItemMargin; + glm::vec2 maxItemSize = (mSize + mItemMargin - grid * mItemMargin) / grid; // Set final children dimensions. for (auto i : mChildren) { @@ -61,8 +58,13 @@ void FlexboxComponent::computeLayout() oldSize.x = maxItemSize.x; glm::vec2 sizeMaxX = {maxItemSize.x, oldSize.y * (maxItemSize.x / oldSize.x)}; glm::vec2 sizeMaxY = {oldSize.x * (maxItemSize.y / oldSize.y), maxItemSize.y}; - glm::vec2 newSize = - sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; + glm::vec2 newSize; + if (sizeMaxX.y > maxItemSize.y) + newSize = sizeMaxY; + else if (sizeMaxY.x > maxItemSize.x) + newSize = sizeMaxX; + else + newSize = sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; i->setSize(newSize); } @@ -106,17 +108,17 @@ void FlexboxComponent::computeLayout() // Translate anchor. if ((i + 1) % std::max(1, static_cast(mItemsPerLine)) != 0) { // Translate on same line. - anchorX += (size.x + mItemMargin.x) * directionLine.x; - anchorY += (size.y + mItemMargin.y) * directionLine.y; + anchorX += (size.x + mItemMargin.x) * static_cast(directionLine.x); + anchorY += (size.y + mItemMargin.y) * static_cast(directionLine.y); } else { // Translate to first position of next line. if (directionRow.x == 0) { - anchorY += lineWidth * directionRow.y; + anchorY += lineWidth * static_cast(directionRow.y); anchorX = anchorXStart; } else { - anchorX += lineWidth * directionRow.x; + anchorX += lineWidth * static_cast(directionRow.x); anchorY = anchorYStart; } } diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index ecf21ecde..bda55e899 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -238,13 +238,13 @@ based on: 'recalbox-multi' by the Recalbox community 0.8125 0.675 - 0.1 0.2 + 0.15 0.21 0 0 row start 3 2 - 0.005 0.005 + 0.0028125 0.005 favorite completed kidgame broken altemulator From 24a480cf73415fd4bcd8040fdf67bc15df42f216 Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Mon, 11 Oct 2021 11:42:21 +0200 Subject: [PATCH 044/128] documentation for the badges --- THEMES-DEV.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/THEMES-DEV.md b/THEMES-DEV.md index 7359ef622..fc6c82f57 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -466,6 +466,8 @@ or to specify only a portion of the value of a theme property: - The "genre" metadata. * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). + * `badges name="md_badges"` - ALL + - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -521,6 +523,8 @@ or to specify only a portion of the value of a theme property: - The "genre" metadata. * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). + * `badges name="md_badges"` - ALL + - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -576,6 +580,8 @@ or to specify only a portion of the value of a theme property: - The "genre" metadata. * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). + * `badges name="md_badges"` - ALL + - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -909,6 +915,45 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice `button_back_XBOX360`, `button_start_XBOX360`. +#### badges + +* `pos` - type: NORMALIZED_PAIR. +* `size` - type: NORMALIZED_PAIR. + - Possible combinations: + - `w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. +* `origin` - type: NORMALIZED_PAIR. + - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the component exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied. +* `direction` - type: STRING. + - Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. +* `align` - type: STRING. + - Valid values are "start", "center", "end", or "stretch". Controls orthogonal alignment to the line axis. "stretch" will stretch the badges to fill-up the line width. +* `itemsPerLine` - type: FLOAT. + - Number of badges that fit on a line. When more badges are available a new line will be started. +* `lines` - type: FLOAT. + - The number of lines available. +* `itemMargin` - type: NORMALIZED_PAIR. + - The margins between badges. Possible combinations: + - `x y` - horizontal and vertical margins. +* `slots` - type: STRING. + - The badge types that should be displayed. Should be specified as a dist of strings separated by spaces. The order will be followed when placing badges on the screen. + - Available badges are: + - "favorite": Will be shown when the game is marked as favorite. + - "completed": Will be shown when the game is marked as completed. + - "kidgame": Will be shown when the game is marked as a kids game. + - "broken": Will be shown when the game is marked as broken. + - "altemulator": Will be shown when an alternative emulator is setup for the game. +* `customBadgeIcon` - type: PATH. + - A badge icon override. Specify the badge type in the attribute `badge`. The available badges are: + `favorite`, + `completed`, + `kidgame`, + `broken`, + `altemulator` +* `visible` - type: BOOLEAN. + - If true, component will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. +* `zIndex` - type: FLOAT. + - z-index value for component. Components will be rendered in order of z-index value from low to high. + #### carousel * `type` - type: STRING. From 848277141a1bfb4ecce0b650e62ad3b8524739ad Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 11 Oct 2021 21:28:37 +0200 Subject: [PATCH 045/128] Optimized the badges code. Also made a small adjustment to the alternative emulator badge. --- .../views/gamelist/DetailedGameListView.cpp | 24 +- .../src/views/gamelist/VideoGameListView.cpp | 24 +- es-core/src/components/BadgesComponent.cpp | 129 +++--- es-core/src/components/BadgesComponent.h | 24 +- es-core/src/components/FlexboxComponent.cpp | 100 ++--- es-core/src/components/FlexboxComponent.h | 54 +-- resources/graphics/badge_altemulator.svg | 388 +++++++++--------- 7 files changed, 360 insertions(+), 383 deletions(-) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 24c1450a9..f8ab72d50 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -404,17 +404,19 @@ void DetailedGameListView::updateInfoPanel() mGenre.setValue(file->metadata.get("genre")); mPlayers.setValue(file->metadata.get("players")); - // Generate badges slots value based on the game metadata. - std::stringstream ss; - ss << (file->metadata.get("favorite").compare("true") ? "" : "favorite "); - ss << (file->metadata.get("completed").compare("true") ? "" : "completed "); - ss << (file->metadata.get("kidgame").compare("true") ? "" : "kidgame "); - ss << (file->metadata.get("broken").compare("true") ? "" : "broken "); - ss << (file->metadata.get("altemulator").compare("") ? "altemulator " : ""); - std::string slots = ss.str(); - if (!slots.empty()) - slots.pop_back(); - mBadges.setValue(slots); + // Populate the badge slots based on game metadata. + std::vector badgeSlots; + for (auto badge : mBadges.getBadgeTypes()) { + if (badge == "altemulator") { + if (file->metadata.get(badge).compare("") != 0) + badgeSlots.push_back(badge); + } + else { + if (file->metadata.get(badge).compare("true") == 0) + badgeSlots.push_back(badge); + } + } + mBadges.setBadges(badgeSlots); mName.setValue(file->metadata.get("name")); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index acff97747..3f5af0828 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -445,17 +445,19 @@ void VideoGameListView::updateInfoPanel() mGenre.setValue(file->metadata.get("genre")); mPlayers.setValue(file->metadata.get("players")); - // Generate badges slots value based on the game metadata. - std::stringstream ss; - ss << (file->metadata.get("favorite").compare("true") ? "" : "favorite "); - ss << (file->metadata.get("completed").compare("true") ? "" : "completed "); - ss << (file->metadata.get("kidgame").compare("true") ? "" : "kidgame "); - ss << (file->metadata.get("broken").compare("true") ? "" : "broken "); - ss << (file->metadata.get("altemulator").compare("") ? "altemulator " : ""); - std::string slots = ss.str(); - if (!slots.empty()) - slots.pop_back(); - mBadges.setValue(slots); + // Populate the badge slots based on game metadata. + std::vector badgeSlots; + for (auto badge : mBadges.getBadgeTypes()) { + if (badge == "altemulator") { + if (file->metadata.get(badge).compare("") != 0) + badgeSlots.push_back(badge); + } + else { + if (file->metadata.get(badge).compare("true") == 0) + badgeSlots.push_back(badge); + } + } + mBadges.setBadges(badgeSlots); mName.setValue(file->metadata.get("name")); diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 1d6ebfaab..6a16f8722 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -7,72 +7,55 @@ // Used by gamelist views. // +#define SLOT_FAVORITE "favorite" +#define SLOT_COMPLETED "completed" +#define SLOT_KIDGAME "kidgame" +#define SLOT_BROKEN "broken" +#define SLOT_ALTERNATIVE_EMULATOR "altemulator" + #include "components/BadgesComponent.h" -#include "Settings.h" #include "ThemeData.h" -#include "resources/TextureResource.h" - -// Available slot definitions. -std::vector BadgesComponent::mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, - SLOT_BROKEN, SLOT_ALTERNATIVE_EMULATOR}; +#include "utils/StringUtil.h" BadgesComponent::BadgesComponent(Window* window) - : FlexboxComponent(window) + : FlexboxComponent{window, mBadgeImages} + , mBadgeTypes{ + {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDGAME, SLOT_BROKEN, SLOT_ALTERNATIVE_EMULATOR}} { mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; - mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.svg"; + mBadgeIcons[SLOT_KIDGAME] = ":/graphics/badge_kidgame.svg"; mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR] = ":/graphics/badge_altemulator.svg"; - - ImageComponent mImageFavorite = ImageComponent(window); - mImageComponents.insert({SLOT_FAVORITE, mImageFavorite}); - ImageComponent mImageCompleted = ImageComponent(window); - mImageComponents.insert({SLOT_COMPLETED, mImageCompleted}); - ImageComponent mImageKids = ImageComponent(window); - mImageComponents.insert({SLOT_KIDS, mImageKids}); - ImageComponent mImageBroken = ImageComponent(window); - mImageComponents.insert({SLOT_BROKEN, mImageBroken}); - ImageComponent mImageAltEmulator = ImageComponent(window); - mImageComponents.insert({SLOT_ALTERNATIVE_EMULATOR, mImageAltEmulator}); } -BadgesComponent::~BadgesComponent() +void BadgesComponent::setBadges(const std::vector& badges) { - for (GuiComponent* c : mChildren) - c->clearChildren(); - clearChildren(); - mBadgeIcons.clear(); - mImageComponents.clear(); -} + std::map prevVisibility; -void BadgesComponent::setValue(const std::string& value) -{ - mChildren.clear(); - if (!value.empty()) { - std::string temp; - std::istringstream ss(value); - while (std::getline(ss, temp, ' ')) { - if (!(temp == SLOT_FAVORITE || temp == SLOT_COMPLETED || temp == SLOT_KIDS || - temp == SLOT_BROKEN || temp == SLOT_ALTERNATIVE_EMULATOR)) - LOG(LogError) << "Badge slot '" << temp << "' is invalid."; - else if (std::find(mSlots.begin(), mSlots.end(), temp) != mSlots.end()) - mChildren.push_back(&mImageComponents.find(temp)->second); - } + // Save the visibility status to know whether any badges changed. + for (auto& image : mBadgeImages) { + prevVisibility[image.first] = image.second.isVisible(); + image.second.setVisible(false); } - onSizeChanged(); -} + for (auto& badge : badges) { + auto it = std::find_if( + mBadgeImages.begin(), mBadgeImages.end(), + [badge](std::pair image) { return image.first == badge; }); -std::string BadgesComponent::getValue() const -{ - std::stringstream ss; - for (auto& slot : mSlots) - ss << slot << ' '; - std::string r = ss.str(); - r.pop_back(); - return r; + if (it != mBadgeImages.cend()) + it->second.setVisible(true); + } + + // Only recalculate the flexbox if any badges changed. + for (auto& image : mBadgeImages) { + if (prevVisibility[image.first] != image.second.isVisible()) { + onSizeChanged(); + break; + } + } } void BadgesComponent::applyTheme(const std::shared_ptr& theme, @@ -82,39 +65,31 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, { using namespace ThemeFlags; - const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); + const ThemeData::ThemeElement* elem{theme->getElement(view, element, "badges")}; if (!elem) return; - for (auto& slot : mSlots) { - if (properties & PATH && elem->has(slot)) { - mBadgeIcons[slot] = elem->get(slot); - mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]); - } - else { - mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]); - std::string teststring; - } - } - if (elem->has("slots")) { - auto value = elem->get("slots"); - mSlots = {}; - if (!value.empty()) { - std::string temp; - std::istringstream ss(value); - while (std::getline(ss, temp, ' ')) { - if (!(temp == SLOT_FAVORITE || temp == SLOT_COMPLETED || temp == SLOT_KIDS || - temp == SLOT_BROKEN || temp == SLOT_ALTERNATIVE_EMULATOR)) - LOG(LogError) << "Badge slot '" << temp << "' is invalid."; - else - mSlots.push_back(temp); + std::vector slots = Utils::String::delimitedStringToVector( + Utils::String::toLower(elem->get("slots")), " "); + + for (auto slot : slots) { + if (std::find(mBadgeTypes.cbegin(), mBadgeTypes.cend(), slot) != mBadgeTypes.end()) { + if (properties & PATH && elem->has(slot)) + mBadgeIcons[slot] = elem->get(slot); + + ImageComponent badgeImage{mWindow}; + + badgeImage.setImage(mBadgeIcons[slot]); + badgeImage.setVisible(false); + mBadgeImages.push_back(std::make_pair(slot, badgeImage)); + } + else { + LOG(LogError) << "Invalid badge slot \"" << slot << "\" defined"; } } + + // Apply theme on the flexbox component parent. + FlexboxComponent::applyTheme(theme, view, element, properties); } - - // Apply theme on the flexbox component parent. - FlexboxComponent::applyTheme(theme, view, element, properties); - - onSizeChanged(); } diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index e7412233a..3b140b742 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -11,30 +11,14 @@ #define ES_CORE_COMPONENTS_BADGES_COMPONENT_H #include "FlexboxComponent.h" -#include "GuiComponent.h" -#include "ImageComponent.h" -#include "renderers/Renderer.h" - -#define NUM_SLOTS 4 -#define SLOT_FAVORITE "favorite" -#define SLOT_COMPLETED "completed" -#define SLOT_KIDS "kidgame" -#define SLOT_BROKEN "broken" -#define SLOT_ALTERNATIVE_EMULATOR "altemulator" - -class TextureResource; class BadgesComponent : public FlexboxComponent { public: BadgesComponent(Window* window); - ~BadgesComponent(); - static std::shared_ptr& getInstance(); - - std::string getValue() const override; - // Should be a list of strings. - void setValue(const std::string& value) override; + std::vector getBadgeTypes() { return mBadgeTypes; } + void setBadges(const std::vector& badges); virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, @@ -42,9 +26,9 @@ public: unsigned int properties) override; private: - static std::vector mSlots; + std::vector mBadgeTypes; std::map mBadgeIcons; - std::map mImageComponents; + std::vector> mBadgeImages; }; #endif // ES_CORE_COMPONENTS_BADGES_COMPONENT_H diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index df65694da..95d41920f 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -7,35 +7,41 @@ // Used by gamelist views. // -#include "components/FlexboxComponent.h" -#include "Settings.h" -#include "ThemeData.h" -#include "resources/TextureResource.h" +#define DEFAULT_DIRECTION Direction::row +#define DEFAULT_ALIGN Align::center +#define DEFAULT_ITEMS_PER_LINE 4 +#define DEFAULT_LINES 1 +#define DEFAULT_MARGIN_X 10.0f +#define DEFAULT_MARGIN_Y 10.0f -FlexboxComponent::FlexboxComponent(Window* window) - : GuiComponent(window) - , mDirection(DEFAULT_DIRECTION) - , mAlign(DEFAULT_ALIGN) - , mItemsPerLine(DEFAULT_ITEMS_PER_LINE) - , mLines(DEFAULT_LINES) - , mItemMargin({DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}) - , mLayoutValid(false) +#include "components/FlexboxComponent.h" + +#include "ThemeData.h" + +FlexboxComponent::FlexboxComponent(Window* window, + std::vector>& images) + : GuiComponent{window} + , mDirection{DEFAULT_DIRECTION} + , mAlign{DEFAULT_ALIGN} + , mImages(images) + , mItemsPerLine{DEFAULT_ITEMS_PER_LINE} + , mLines{DEFAULT_LINES} + , mItemMargin{glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}} + , mLayoutValid{false} { } -void FlexboxComponent::onSizeChanged() { mLayoutValid = false; } - void FlexboxComponent::computeLayout() { // Start placing items in the top-left. - float anchorX = 0; - float anchorY = 0; - float anchorOriginX = 0; - float anchorOriginY = 0; + float anchorX{0.0f}; + float anchorY{0.0f}; + float anchorOriginX{0.0f}; + float anchorOriginY{0.0f}; // Translation directions when placing items. - glm::ivec2 directionLine = {1, 0}; - glm::ivec2 directionRow = {0, 1}; + glm::ivec2 directionLine{1, 0}; + glm::ivec2 directionRow{0, 1}; // Change direction. if (mDirection == Direction::column) { @@ -43,21 +49,23 @@ void FlexboxComponent::computeLayout() directionRow = {1, 0}; } - // Compute children maximal dimensions. + // Compute maximum image dimensions. glm::vec2 grid; if (mDirection == Direction::row) grid = {mItemsPerLine, mLines}; else grid = {mLines, mItemsPerLine}; - glm::vec2 maxItemSize = (mSize + mItemMargin - grid * mItemMargin) / grid; + glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid}; - // Set final children dimensions. - for (auto i : mChildren) { - auto oldSize = i->getSize(); + // Set final image dimensions. + for (auto& image : mImages) { + if (!image.second.isVisible()) + continue; + auto oldSize{image.second.getSize()}; if (oldSize.x == 0) oldSize.x = maxItemSize.x; - glm::vec2 sizeMaxX = {maxItemSize.x, oldSize.y * (maxItemSize.x / oldSize.x)}; - glm::vec2 sizeMaxY = {oldSize.x * (maxItemSize.y / oldSize.y), maxItemSize.y}; + glm::vec2 sizeMaxX{maxItemSize.x, oldSize.y * (maxItemSize.x / oldSize.x)}; + glm::vec2 sizeMaxY{oldSize.x * (maxItemSize.y / oldSize.y), maxItemSize.y}; glm::vec2 newSize; if (sizeMaxX.y > maxItemSize.y) newSize = sizeMaxY; @@ -65,23 +73,27 @@ void FlexboxComponent::computeLayout() newSize = sizeMaxX; else newSize = sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; - i->setSize(newSize); + image.second.setResize(newSize.x, newSize.y); } // Pre-compute layout parameters. float lineWidth = (mDirection == Direction::row ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); - float anchorXStart = anchorX; - float anchorYStart = anchorY; + float anchorXStart{anchorX}; + float anchorYStart{anchorY}; - // Iterate through the children. - for (int i = 0; i < static_cast(mChildren.size()); i++) { - GuiComponent* child = mChildren[i]; - auto size = child->getSize(); + int i = 0; + + // Iterate through the images. + for (auto& image : mImages) { + if (!image.second.isVisible()) + continue; + + auto size{image.second.getSize()}; // Top-left anchor position. - float x = anchorX - anchorOriginX * size.x; - float y = anchorY - anchorOriginY * size.y; + float x{anchorX - anchorOriginX * size.x}; + float y{anchorY - anchorOriginY * size.y}; // Apply alignment if (mAlign == Align::end) { @@ -93,7 +105,7 @@ void FlexboxComponent::computeLayout() y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; } else if (mAlign == Align::stretch && mDirection == Direction::row) { - child->setSize(child->getSize().x, maxItemSize.y); + image.second.setSize(image.second.getSize().x, maxItemSize.y); } // Apply origin. @@ -103,10 +115,10 @@ void FlexboxComponent::computeLayout() y -= mOrigin.y * mSize.y; // Store final item position. - child->setPosition(getPosition().x + x, getPosition().y + y); + image.second.setPosition(getPosition().x + x, getPosition().y + y); // Translate anchor. - if ((i + 1) % std::max(1, static_cast(mItemsPerLine)) != 0) { + if ((i++ + 1) % std::max(1, static_cast(mItemsPerLine)) != 0) { // Translate on same line. anchorX += (size.x + mItemMargin.x) * static_cast(directionLine.x); anchorY += (size.y + mItemMargin.y) * static_cast(directionLine.y); @@ -135,7 +147,8 @@ void FlexboxComponent::render(const glm::mat4& parentTrans) if (!mLayoutValid) computeLayout(); - renderChildren(parentTrans); + for (auto& image : mImages) + image.second.render(parentTrans); } void FlexboxComponent::applyTheme(const std::shared_ptr& theme, @@ -149,7 +162,6 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, glm::vec2{static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())}}; - // TODO: How to do this without explicit 'badges' property? const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); if (!elem) return; @@ -179,9 +191,3 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, // Layout no longer valid. mLayoutValid = false; } - -std::vector FlexboxComponent::getHelpPrompts() -{ - std::vector prompts; - return prompts; -} diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index ad7ee90db..c278b1aed 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -11,67 +11,75 @@ #define ES_CORE_COMPONENTS_FLEXBOX_COMPONENT_H #include "GuiComponent.h" -#include "renderers/Renderer.h" - -// Default values. -#define DEFAULT_DIRECTION Direction::row -#define DEFAULT_ALIGN Align::center -#define DEFAULT_ITEMS_PER_LINE 4 -#define DEFAULT_LINES 1 -#define DEFAULT_MARGIN_X 10.0f -#define DEFAULT_MARGIN_Y 10.0f +#include "components/ImageComponent.h" class FlexboxComponent : public GuiComponent { public: - enum class Direction : char { row, column }; - enum class Align : char { start, end, center, stretch }; + enum class Direction : char { + row, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). + column + }; - explicit FlexboxComponent(Window* window); + enum class Align : char { + start, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). + end, + center, + stretch + }; + + explicit FlexboxComponent(Window* window, + std::vector>& images); // Getters/Setters for rendering options. - [[nodiscard]] Align getAlign() const { return mAlign; }; + Align getAlign() const { return mAlign; } void setAlign(Align value) { mAlign = value; mLayoutValid = false; - }; - [[nodiscard]] unsigned int getItemsPerLine() const { return mItemsPerLine; }; + } + + unsigned int getItemsPerLine() const { return mItemsPerLine; } void setItemsPerLine(unsigned int value) { mItemsPerLine = value; mLayoutValid = false; - }; - [[nodiscard]] unsigned int getLines() const { return mLines; }; + } + + unsigned int getLines() const { return mLines; } void setLines(unsigned int value) { mLines = value; mLayoutValid = false; - }; - [[nodiscard]] glm::vec2 getItemMargin() const { return mItemMargin; }; + } + + glm::vec2 getItemMargin() const { return mItemMargin; } void setItemMargin(glm::vec2 value) { mItemMargin = value; mLayoutValid = false; - }; + } - void onSizeChanged() override; + void onSizeChanged() override { mLayoutValid = false; } void render(const glm::mat4& parentTrans) override; void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; - std::vector getHelpPrompts() override; private: // Calculate flexbox layout. void computeLayout(); - // Rendering options. + // Layout options. Direction mDirection; Align mAlign; + + std::vector>& mImages; + unsigned int mItemsPerLine; unsigned int mLines; + glm::vec2 mItemMargin; bool mLayoutValid; }; diff --git a/resources/graphics/badge_altemulator.svg b/resources/graphics/badge_altemulator.svg index 0a54263ac..0a07c9872 100644 --- a/resources/graphics/badge_altemulator.svg +++ b/resources/graphics/badge_altemulator.svg @@ -1,48 +1,48 @@ + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="80" + height="112" + viewBox="0 0 21.166666 29.633334" + version="1.1" + id="svg4842" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" + sodipodi:docname="badge_altemulator.svg"> + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="12.442154" + inkscape:cx="-18.41824" + inkscape:cy="59.681612" + inkscape:document-units="mm" + inkscape:current-layer="layer2" + showgrid="false" + inkscape:window-width="3840" + inkscape:window-height="2065" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + units="px" + inkscape:snap-nodes="false" + inkscape:snap-others="false" + inkscape:snap-global="true" + inkscape:document-rotation="0" /> @@ -51,7 +51,7 @@ image/svg+xml - + @@ -61,47 +61,47 @@ id="layer1" transform="translate(-1.9829021e-4,-266.11715)"> + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56896;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + id="rect5286" + width="21.166666" + height="29.633333" + x="0.0001982902" + y="266.11713" /> + x="3.0257001" + y="2.9029" + width="204.36" + height="288.81" + id="rect14" + style="fill:#d7d7d7;fill-opacity:1;stroke-width:0.26458" /> + x="7.7007999" + y="7.7174001" + width="194.89999" + height="279.22" + id="rect18" + style="stroke-width:0.26458" /> + style="fill:#e6e6e6;fill-opacity:1;stroke-width:0.0266453" + id="rect22" + height="17.395565" + width="17.540144" + y="271.47574" + x="1.8325089" /> + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.25971" + id="g56-4" + aria-label="CONTENT RATED BY" + transform="matrix(0.09632353,0,0,0.10097609,0.27971034,266.32293)"> + d="m 2.2271284,292.95117 c 0,0.40604 0.015331,0.79582 0.030663,1.17749 0,0.0243 0.030663,0.0406 0.076656,0.0406 0.2529687,0.008 1.364477,0.0203 1.625048,0.0203 0.2529687,0 1.073192,-0.004 1.3184891,-0.0122 v -0.29642 -0.35327 c -0.00767,-0.0203 -0.038327,-0.0406 -0.076656,-0.0406 -0.2606263,-0.004 -1.096179,-0.008 -1.3568192,-0.008 h -0.099653 v -0.21114 h 0.9965315 v -0.268 -0.30859 c -0.00767,-0.0203 -0.038327,-0.0406 -0.076656,-0.0406 -0.1993091,0 -0.6132565,-0.004 -0.919871,-0.004 v -0.21521 h 0.2989565 c 0.2529687,0 0.9735306,0 1.2188276,-0.008 v -0.29642 -0.35326 c -0.00767,-0.0203 -0.038327,-0.0406 -0.076656,-0.0406 -0.2606264,-0.004 -1.0808497,-0.008 -1.3414899,-0.008 -0.2529687,0 -1.3491477,0.004 -1.5867594,0.008 -0.022997,0.40604 -0.030663,0.81206 -0.030663,1.21812 z" + id="path78-4" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.036704" /> + d="m 5.6153964,294.14527 c 0.5289244,0.0325 1.0961791,0.0446 1.6174179,0.0446 1.0348618,0 1.8780861,-0.15023 1.8780861,-0.79582 0,-0.5644 -0.9505434,-0.71463 -1.5867594,-0.81618 -0.3142859,-0.0487 -0.5519252,-0.0853 -0.5519252,-0.15834 0,-0.0731 0.1533072,-0.1137 0.3909464,-0.1137 0.4752649,0 1.0195325,0.0812 1.3798065,0.16647 l 0.1763081,-0.67809 c -0.5135951,-0.0527 -1.0348617,-0.0894 -1.4795233,-0.0894 -1.0501911,0 -1.8167688,0.0487 -1.8167688,0.70652 0,0.62531 0.8508821,0.75525 1.4258361,0.84049 0.2989567,0.0446 0.5212666,0.0772 0.5212666,0.15836 0,0.065 -0.1226499,0.14617 -0.4139474,0.14617 -0.3449586,0 -0.8202234,-0.0528 -1.3568193,-0.14212 z" + id="path80-9" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.036704" /> + d="m 9.4307297,292.9475 c 0,0.10149 0.00613,0.19489 0.01226,0.29641 0,0.0243 0.024523,0.0406 0.061303,0.0406 0.2023048,0.008 0.9931183,0.0203 1.2015133,0.0203 0.202306,0 0.919554,-0.004 1.054426,-0.0122 0,-0.0853 0.0061,-0.17459 0.0061,-0.2558 0,-0.20302 -0.0061,-0.24769 -0.0061,-0.34514 -0.0061,-0.0203 -0.03065,-0.0406 -0.06131,-0.0406 -0.208428,-0.004 -0.692729,-0.008 -0.901169,-0.008 -0.202303,0 -1.1647903,0.004 -1.354836,0.008 -0.00613,0.13399 -0.01226,0.21926 -0.01226,0.29642 z" + id="path82-1" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.0328234" /> + d="m 13.651876,292.95117 c 0,-0.19489 0,-0.36544 0.0077,-0.53598 h 0.130314 c 0.206967,0 0.360288,0.0528 0.360288,0.53598 0,0.48319 -0.153307,0.54004 -0.360288,0.54004 h -0.130314 c -0.0077,-0.17054 -0.0077,-0.34108 -0.0077,-0.54004 z m -1.517812,0 c 0,0.40604 0.01533,0.75525 0.01533,1.13692 0,0.0243 0.03066,0.0406 0.07666,0.0406 0.528925,0.0284 1.011861,0.0487 1.433466,0.0487 1.28783,0 2.092697,-0.21115 2.092697,-1.22628 0,-0.95418 -0.812553,-1.24657 -2.108095,-1.24657 -0.429277,0 -0.98886,0.0284 -1.494784,0.0691 0,0.40604 -0.01533,0.7715 -0.01533,1.17749 z" + id="path84-7" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.036704" /> + d="m 15.999759,292.92458 c 0,0.3953 0.01492,0.77476 0.02985,1.14634 0,0.0237 0.02985,0.0395 0.07463,0.0395 0.246278,0.008 1.328384,0.0198 1.582062,0.0198 0.246277,0 1.044804,-0.004 1.283611,-0.0118 v -0.28858 -0.34391 c -0.0075,-0.0198 -0.03731,-0.0395 -0.07463,-0.0395 -0.253731,-0.004 -1.067182,-0.008 -1.320928,-0.008 h -0.09702 v -0.20557 h 0.970171 v -0.2609 -0.30043 c -0.0075,-0.0198 -0.03731,-0.0395 -0.07463,-0.0395 -0.194037,0 -0.597034,-0.004 -0.895539,-0.004 v -0.20951 h 0.291049 c 0.246277,0 0.947778,0 1.186587,-0.008 v -0.28857 -0.34391 c -0.0075,-0.0198 -0.03731,-0.0395 -0.07463,-0.0395 -0.253731,-0.004 -1.052258,-0.008 -1.306003,-0.008 -0.246277,0 -1.313459,0.004 -1.544785,0.008 -0.02239,0.39529 -0.02985,0.79057 -0.02985,1.18589 z" + id="path86-0" + inkscape:connector-curvature="0" + style="fill:#f0f0f0;fill-opacity:1;stroke-width:0.0357331" /> + id="g54" + aria-label="CONTENT RATED BY" + transform="matrix(0.09871346,0,0,0.10292059,-29.557342,270.26518)" + style="fill:#ffffff;stroke-width:0.25912"> + id="path26" + d="m 17.44,237.05 c 0,6.2189 2.6119,7.5125 6.7911,7.5125 1.0199,0 1.8408,-0.0746 2.8607,-0.27364 l -0.52239,-3.9304 -2.2886,0.17413 c -1.0199,0.0497 -1.6667,-0.52239 -1.6667,-3.4826 0,-2.9602 0.74627,-3.5075 1.6667,-3.4577 l 2.2886,0.14926 0.54726,-3.9055 c -1.0199,-0.22388 -1.8159,-0.42289 -2.8358,-0.42289 -4.204,0 -6.8408,1.791 -6.8408,7.6368 z" + inkscape:connector-curvature="0" /> + id="path28" + d="m 27.505,237.05 c 0,5.7712 2.1642,7.612 6.9154,7.612 3.5075,-0.17413 5.7214,-2.7114 5.7214,-7.612 0,-5.7463 -2.2388,-7.6368 -6.9652,-7.6368 -3.5075,0.17413 -5.6717,2.7612 -5.6717,7.6368 z m 5.1741,0 c 0,-2.9602 0.42289,-3.2836 1.0945,-3.2836 0.67161,0 1.1692,0.32339 1.1692,3.2836 0,2.9602 -0.42289,3.2836 -1.0945,3.2836 -0.67161,0 -1.1692,-0.32339 -1.1692,-3.2836 z" + inkscape:connector-curvature="0" /> + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Dyuthi;-inkscape-font-specification:Dyuthi;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="19.372959" + y="271.49698" + id="text5145" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 11.142864,276.8821 c 0,-0.13102 -0.08756,-0.2366 -0.19606,-0.2366 h -0.245083 v -0.8903 h -0.444919 v 0.8903 h -0.245074 c -0.1085141,0 -0.1960697,0.10558 -0.1960697,0.2366 v 0.80877 c 0,0.13102 0.087556,0.23659 0.1960697,0.23659 h 0.935076 c 0.108505,0 0.19606,-0.10557 0.19606,-0.23659 z m -0.437842,0.17863 v 0.45151 h -0.451513 v -0.45151 z" + id="rect5069-0-5-6-6" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 5.8349883,276.8821 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 5.3938449 v -0.8903 H 4.9489256 v 0.8903 H 4.703852 c -0.1085142,0 -0.1960698,0.10558 -0.1960698,0.2366 v 0.80877 c 0,0.13102 0.087556,0.23659 0.1960698,0.23659 h 0.9350756 c 0.1085051,0 0.1960607,-0.10557 0.1960607,-0.23659 z m -0.4378418,0.17863 v 0.45151 H 4.9456332 v -0.45151 z" + id="rect5069-0-5-1-0-8-8" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 7.605691,276.8821 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 7.1645475 v -0.8903 H 6.7196374 v 0.8903 H 6.4745547 c -0.1085143,0 -0.1960608,0.10558 -0.1960608,0.2366 v 0.80877 c 0,0.13102 0.087556,0.23659 0.1960608,0.23659 h 0.9350756 c 0.1085051,0 0.1960607,-0.10557 0.1960607,-0.23659 z m -0.4378419,0.17863 v 0.45151 H 6.7163358 v -0.45151 z" + id="rect5069-0-5-1-0-4" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 9.3740406,276.8821 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 8.9328972 v -0.8903 H 8.487987 v 0.8903 H 8.2429043 c -0.1085143,0 -0.1960607,0.10558 -0.1960607,0.2366 v 0.80877 c 0,0.13102 0.087556,0.23659 0.1960607,0.23659 h 0.9350756 c 0.1085051,0 0.1960607,-0.10557 0.1960607,-0.23659 z m -0.4378419,0.17863 v 0.45151 H 8.4846854 v -0.45151 z" + id="rect5069-0-5-1-7" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 12.916869,276.8821 c 0,-0.13102 -0.08756,-0.2366 -0.196061,-0.2366 h -0.245083 v -0.8903 h -0.444919 v 0.8903 h -0.245074 c -0.108514,0 -0.19607,0.10558 -0.19607,0.2366 v 0.80877 c 0,0.13102 0.08756,0.23659 0.19607,0.23659 h 0.935076 c 0.108505,0 0.196061,-0.10557 0.196061,-0.23659 z m -0.437842,0.17863 v 0.45151 h -0.451514 v -0.45151 z" + id="rect5069-0-5-2" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 14.686158,276.8821 c 0,-0.13102 -0.08756,-0.2366 -0.196061,-0.2366 h -0.245083 v -0.8903 h -0.44491 v 0.8903 h -0.245083 c -0.108514,0 -0.196061,0.10558 -0.196061,0.2366 v 0.80877 c 0,0.13102 0.08756,0.23659 0.196061,0.23659 h 0.935076 c 0.108505,0 0.196061,-0.10557 0.196061,-0.23659 z m -0.437842,0.17863 v 0.45151 h -0.451514 v -0.45151 z" + id="rect5069-0-4" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 11.142864,282.65618 c 0,-0.13102 -0.08756,-0.2366 -0.19606,-0.2366 h -0.935076 c -0.1084776,0 -0.1960697,0.10558 -0.1960697,0.2366 v 0.80876 c 0,0.13102 0.087556,0.2366 0.1960697,0.2366 h 0.245074 v 0.8903 h 0.444919 v -0.8903 h 0.245083 c 0.108477,0 0.19606,-0.10558 0.19606,-0.2366 z m -0.437842,0.17862 v 0.45152 h -0.451513 v -0.45152 z" + id="rect5069-0-5-6" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 16.452628,282.65618 c 0,-0.13102 -0.08755,-0.2366 -0.19607,-0.2366 h -0.935075 c -0.108478,0 -0.196061,0.10558 -0.196061,0.2366 v 0.80876 c 0,0.13102 0.08756,0.2366 0.196061,0.2366 h 0.245083 v 0.8903 h 0.444919 v -0.8903 h 0.245073 c 0.108478,0 0.19607,-0.10558 0.19607,-0.2366 z m -0.437851,0.17862 v 0.45152 h -0.451513 v -0.45152 z" + id="rect5195" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 5.8349883,282.65618 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 4.703852 c -0.1084777,0 -0.1960698,0.10558 -0.1960698,0.2366 v 0.80876 c 0,0.13102 0.087556,0.2366 0.1960698,0.2366 h 0.2450736 v 0.8903 h 0.4449193 v -0.8903 h 0.2450827 c 0.1084778,0 0.1960607,-0.10558 0.1960607,-0.2366 z m -0.4378418,0.17862 v 0.45152 H 4.9456332 v -0.45152 z" + id="rect5069-0-5-1-0-8" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 7.605691,282.65618 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 6.4745547 c -0.1084778,0 -0.1960608,0.10558 -0.1960608,0.2366 v 0.80876 c 0,0.13102 0.087556,0.2366 0.1960608,0.2366 h 0.2450827 v 0.8903 h 0.4449101 v -0.8903 h 0.2450828 c 0.1084777,0 0.1960607,-0.10558 0.1960607,-0.2366 z m -0.4378419,0.17862 v 0.45152 H 6.7163358 v -0.45152 z" + id="rect5069-0-5-1-0" + inkscape:connector-curvature="0" /> + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" + d="m 9.3740406,282.65618 c 0,-0.13102 -0.087556,-0.2366 -0.1960607,-0.2366 H 8.2429043 c -0.1084778,0 -0.1960607,0.10558 -0.1960607,0.2366 v 0.80876 c 0,0.13102 0.087556,0.2366 0.1960607,0.2366 H 8.487987 v 0.8903 h 0.4449102 v -0.8903 h 0.2450827 c 0.1084777,0 0.1960607,-0.10558 0.1960607,-0.2366 z m -0.4378419,0.17862 v 0.45152 H 8.4846854 v -0.45152 z" + id="rect5069-0-5-1" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 12.916869,282.65618 c 0,-0.13102 -0.08756,-0.2366 -0.196061,-0.2366 h -0.935076 c -0.108478,0 -0.19607,0.10558 -0.19607,0.2366 v 0.80876 c 0,0.13102 0.08756,0.2366 0.19607,0.2366 h 0.245074 v 0.8903 h 0.444919 v -0.8903 h 0.245083 c 0.108478,0 0.196061,-0.10558 0.196061,-0.2366 z m -0.437842,0.17862 v 0.45152 h -0.451514 v -0.45152 z" + id="rect5069-0-5" + inkscape:connector-curvature="0" /> + style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.221985;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + d="m 14.686158,282.65618 c 0,-0.13102 -0.08756,-0.2366 -0.196061,-0.2366 h -0.935076 c -0.108478,0 -0.196061,0.10558 -0.196061,0.2366 v 0.80876 c 0,0.13102 0.08756,0.2366 0.196061,0.2366 h 0.245083 v 0.8903 h 0.44491 v -0.8903 h 0.245083 c 0.108478,0 0.196061,-0.10558 0.196061,-0.2366 z m -0.437842,0.17862 v 0.45152 h -0.451514 v -0.45152 z" + id="rect5069-0" + inkscape:connector-curvature="0" /> + id="rect5061" + d="m 4.7486749,277.07833 c -0.3682172,0 -0.664559,0.25094 -0.664559,0.56276 v 5.0648 c 0,0.31182 0.2963418,0.56276 0.664559,0.56276 H 16.456487 c 0.368217,0 0.664559,-0.25094 0.664559,-0.56276 v -1.64279 a 0.91714642,0.91714642 0 0 1 -0.698149,-0.88987 0.91714642,0.91714642 0 0 1 0.698149,-0.88935 v -1.64279 c 0,-0.31182 -0.296342,-0.56276 -0.664559,-0.56276 z" + style="opacity:1;fill:#fcfcfd;fill-opacity:1;stroke:#282828;stroke-width:0.274902;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + inkscape:connector-curvature="0" /> + rx="0.42198506" + ry="0.42198506" + y="277.72012" + x="7.7069221" + height="4.9067254" + width="5.7913175" + id="rect5019" + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.213968;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" /> + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Layer 2"> + aria-label="ALT EMU" + transform="scale(1.0680187,0.93631321)" + id="text41" + style="font-size:4.97619px;line-height:1.25;font-family:'Bebas Neue';-inkscape-font-specification:'Bebas Neue';display:inline;stroke-width:0.0339285;fill:#f0f0f0;fill-opacity:1"> + d="m 1.7229501,4.7457954 c 0,0.024881 0.019905,0.03981 0.044786,0.03981 0.1642143,0.00995 0.3383809,0.014928 0.5075714,0.014928 0.1642142,0 0.2537857,-0.00498 0.4130238,-0.014928 0.034833,-0.1791429 0.06469,-0.3632619 0.094548,-0.547381 h 0.4627856 c 0.024881,0.1691905 0.054738,0.338381 0.089571,0.4976191 0.00498,0.029857 0.029857,0.049762 0.054738,0.049762 0.1741666,0.014928 0.3184761,0.014928 0.4876666,0.014928 0.1691904,0 0.2786666,-0.00498 0.4329285,-0.014928 C 4.1662594,3.8202241 3.9323785,2.8050813 3.7432833,1.8496528 3.7383071,1.8247719 3.7134261,1.7998909 3.6885452,1.7998909 3.449688,1.7949147 3.2158071,1.7899385 2.97695,1.7899385 c -0.233881,0 -0.4627857,0 -0.6867143,0.00995 -0.184119,0.9205952 -0.393119,1.8809999 -0.5424047,2.7916426 -0.00995,0.049762 -0.024881,0.1443096 -0.024881,0.1542619 z M 2.8674738,3.5913193 c 0.03981,-0.3732142 0.079619,-0.7464285 0.1244047,-1.1146665 0.049762,0.3732142 0.099524,0.7464285 0.1542619,1.1146665 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.97619px;font-family:Digitalt;-inkscape-font-specification:Digitalt;fill:#f0f0f0;stroke-width:0.0339285;fill-opacity:1" + id="path975" /> + d="m 4.4946808,3.2927479 c 0,0.497619 0.00995,0.9753333 0.019905,1.4430952 0,0.029857 0.019905,0.049762 0.049762,0.049762 0.1642143,0.00995 0.8857619,0.024881 1.0549523,0.024881 0.1642143,0 0.5772381,-0.00498 0.7364762,-0.014929 0,-0.1244047 0,-0.2438333 0,-0.3632618 0,-0.1393334 0,-0.2786667 0,-0.4329286 C 6.3507997,3.974486 6.3308949,3.949605 6.306014,3.949605 6.1368235,3.9446288 5.7138474,3.9396526 5.5446569,3.9396526 h -0.019905 c 0,-0.2089999 0.00498,-0.4229761 0.00498,-0.6469047 0,-0.497619 -0.00995,-0.9753332 -0.019905,-1.4430951 0,-0.024881 -0.024881,-0.049762 -0.049762,-0.049762 -0.1691905,-0.00498 -0.3433572,-0.00995 -0.5125476,-0.00995 -0.1642143,0 -0.2786667,0.00498 -0.4329286,0.00995 -0.014928,0.497619 -0.019905,0.995238 -0.019905,1.492857 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.97619px;font-family:Digitalt;-inkscape-font-specification:Digitalt;fill:#f0f0f0;stroke-width:0.0339285;fill-opacity:1" + id="path977" /> + d="m 6.1318408,2.1780813 c 0,0.1343572 0,0.2687143 0.00498,0.4080476 0,0.029857 0.019905,0.049762 0.049762,0.049762 0.084595,0.00498 0.3234523,0.00995 0.5473809,0.014929 0,0.209 0,0.4130238 0,0.6419285 0,0.497619 0.00995,0.9753333 0.019905,1.4430952 0,0.029857 0.019905,0.049762 0.049762,0.049762 0.1642143,0.00995 0.338381,0.014928 0.5075714,0.014928 0.1642143,0 0.2786667,-0.00498 0.4379048,-0.014928 0.00995,-0.4976191 0.019905,-0.9952381 0.019905,-1.4928571 0,-0.2289047 -0.00498,-0.4329285 -0.00995,-0.6369523 0.2587619,0 0.5225,-0.00498 0.6070952,-0.00995 0,-0.1244047 0,-0.2488095 0,-0.3682381 0,-0.1443095 0,-0.2836428 0,-0.4279523 -0.00498,-0.024881 -0.024881,-0.049762 -0.049762,-0.049762 -0.1691904,-0.00498 -0.8608809,-0.00995 -1.0300713,-0.00995 -0.1642143,0 -0.995238,0.00498 -1.1494999,0.00995 -0.00498,0.1343571 -0.00498,0.2587619 -0.00498,0.3781904 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.97619px;font-family:Digitalt;-inkscape-font-specification:Digitalt;fill:#f0f0f0;stroke-width:0.0339285;fill-opacity:1" + id="path979" /> + d="m 10.067999,3.2927479 c 0,0.497619 0.01,0.9753333 0.0199,1.4430952 0,0.029857 0.0199,0.049762 0.04976,0.049762 0.164215,0.00995 0.885762,0.024881 1.054953,0.024881 0.164214,0 0.696666,-0.00498 0.855904,-0.014929 0,-0.1244047 0,-0.2438333 0,-0.3632618 0,-0.1393334 0,-0.2786667 0,-0.4329286 -0.005,-0.024881 -0.02488,-0.049762 -0.04976,-0.049762 -0.169191,-0.00498 -0.711596,-0.00995 -0.880786,-0.00995 h -0.06469 c 0,-0.084595 0,-0.1691904 0,-0.2587618 h 0.646905 c 0,-0.1194286 0,-0.2239286 0,-0.3284286 0,-0.1194286 0,-0.2388571 0,-0.3781904 -0.005,-0.024881 -0.02488,-0.049762 -0.04976,-0.049762 -0.129381,0 -0.398095,-0.00498 -0.597143,-0.00498 0,-0.089571 0,-0.1791429 0,-0.2637381 0.0846,0 0.154262,0 0.194072,0 0.164214,0 0.631976,0 0.791214,-0.00995 0,-0.1244047 0,-0.2438333 0,-0.3632619 0,-0.1393333 0,-0.2786666 0,-0.4329285 -0.005,-0.024881 -0.02488,-0.049762 -0.04976,-0.049762 -0.16919,-0.00498 -0.701643,-0.00995 -0.870833,-0.00995 -0.164214,0 -0.87581,0.00498 -1.030071,0.00995 -0.01493,0.497619 -0.0199,0.995238 -0.0199,1.492857 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.97619px;font-family:Digitalt;-inkscape-font-specification:Digitalt;fill:#f0f0f0;stroke-width:0.0339285;fill-opacity:1" + id="path981" /> + d="m 12.327182,3.2927479 c 0,0.497619 0.01,0.9753333 0.0199,1.4430952 0,0.029857 0.01991,0.049762 0.04976,0.049762 0.164215,0.00995 0.363262,0.014928 0.482691,0.014928 0.164214,0 0.253786,-0.00498 0.413024,-0.014928 l -0.0199,-1.3485476 0.542405,1.099738 0.04976,0.00995 0.05474,-0.00995 0.567285,-1.1544761 c 0,0.4677619 0.0199,0.915619 0.02986,1.3535238 0,0.029857 0.0199,0.049762 0.04976,0.049762 0.164215,0.00995 0.338381,0.014928 0.45781,0.014928 0.164214,0 0.243833,-0.00498 0.388143,-0.014928 0.01,-0.4976191 0.0199,-0.9952381 0.0199,-1.4928571 0,-0.497619 -0.01,-0.9753332 -0.0199,-1.4430951 0,-0.024881 -0.02488,-0.044786 -0.04976,-0.049762 -0.139334,-0.00498 -0.293596,-0.00995 -0.462786,-0.00995 -0.139333,0 -0.258762,0.00498 -0.348333,0.014929 -0.02488,0 -0.04479,0.024881 -0.05972,0.044786 l -0.627,1.2788809 -0.622023,-1.2788809 c -0.01,-0.019905 -0.02986,-0.044786 -0.05474,-0.049762 -0.149286,-0.00498 -0.263738,-0.00995 -0.432929,-0.00995 -0.164214,0 -0.253786,0.00498 -0.408048,0.00995 -0.01493,0.497619 -0.0199,0.995238 -0.0199,1.492857 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.97619px;font-family:Digitalt;-inkscape-font-specification:Digitalt;fill:#f0f0f0;stroke-width:0.0339285;fill-opacity:1" + id="path983" /> + d="m 15.696059,3.3126527 c 0,1.2539999 0.447857,1.5227142 1.348547,1.5227142 0.701643,-0.034833 1.109691,-0.442881 1.109691,-1.5227142 0,-0.497619 -0.01,-0.995238 -0.01991,-1.4629999 0,-0.024881 -0.02488,-0.049762 -0.04976,-0.049762 -0.16919,-0.00498 -0.343357,-0.00995 -0.512548,-0.00995 -0.164214,0 -0.278666,0.00498 -0.432928,0.00995 -0.01493,0.497619 -0.01991,1.1644285 -0.01991,1.6620475 0,0.4428809 -0.05474,0.5075714 -0.189095,0.5075714 -0.134357,0 -0.199048,-0.06469 -0.199048,-0.5075714 0,-0.497619 -0.01,-1.1445237 -0.0199,-1.6122856 0,-0.024881 -0.02488,-0.049762 -0.04976,-0.049762 -0.169191,-0.00498 -0.343357,-0.00995 -0.512548,-0.00995 -0.164214,0 -0.278667,0.00498 -0.432928,0.00995 -0.01493,0.497619 -0.0199,1.0151428 -0.0199,1.5127618 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.97619px;font-family:Digitalt;-inkscape-font-specification:Digitalt;fill:#f0f0f0;stroke-width:0.0339285;fill-opacity:1" + id="path985" /> From ae96cb4c549d3409ea6a41979beaa24b4b58d2fa Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 22:53:02 +0200 Subject: [PATCH 046/128] Further improvements to the badges code. --- .../views/gamelist/DetailedGameListView.cpp | 27 +++-- .../src/views/gamelist/DetailedGameListView.h | 2 +- .../src/views/gamelist/VideoGameListView.cpp | 28 +++-- es-app/src/views/gamelist/VideoGameListView.h | 2 +- es-core/src/ThemeData.cpp | 10 +- es-core/src/ThemeData.h | 1 - es-core/src/components/BadgesComponent.cpp | 106 +++++++++++++++-- es-core/src/components/BadgesComponent.h | 10 +- es-core/src/components/FlexboxComponent.cpp | 111 +++++++----------- es-core/src/components/FlexboxComponent.h | 53 ++++----- 10 files changed, 214 insertions(+), 136 deletions(-) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index f8ab72d50..d17c7297c 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -29,7 +29,6 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) , mLblPlayers(window) , mLblLastPlayed(window) , mLblPlayCount(window) - , mBadges(window) , mRating(window) , mReleaseDate(window) , mDeveloper(window) @@ -39,6 +38,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) , mLastPlayed(window) , mPlayCount(window) , mName(window) + , mBadges(window) , mDescContainer(window) , mDescription(window) , mGamelistInfo(window) @@ -94,7 +94,6 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) mLblPlayers.setText("Players: "); addChild(&mLblPlayers); addChild(&mPlayers); - addChild(&mBadges); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); mLastPlayed.setDisplayRelative(true); @@ -103,6 +102,13 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) addChild(&mLblPlayCount); addChild(&mPlayCount); + // Badges. + addChild(&mBadges); + mBadges.setOrigin(0.0f, 0.0f); + mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); + mBadges.setSize(mSize.x * 0.15, mSize.y * 0.2f); + mBadges.setDefaultZIndex(50.0f); + mName.setPosition(mSize.x, mSize.y); mName.setDefaultZIndex(40.0f); mName.setColor(0xAAAAAAFF); @@ -143,6 +149,7 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr& them mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); + mBadges.applyTheme(theme, getName(), "md_badges", ALL); initMDLabels(); std::vector labels = getMDLabels(); @@ -156,10 +163,10 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr& them initMDValues(); std::vector values = getMDValues(); - assert(values.size() == 9); - std::vector valElements = {"md_rating", "md_releasedate", "md_developer", - "md_publisher", "md_genre", "md_players", - "md_badges", "md_lastplayed", "md_playcount"}; + assert(values.size() == 8); + std::vector valElements = {"md_rating", "md_releasedate", "md_developer", + "md_publisher", "md_genre", "md_players", + "md_lastplayed", "md_playcount"}; for (unsigned int i = 0; i < values.size(); i++) values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); @@ -226,8 +233,6 @@ void DetailedGameListView::initMDValues() mLastPlayed.setFont(defaultFont); mPlayCount.setFont(defaultFont); - mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast(defaultFont->getHeight())); - float bottom = 0.0f; const float colSize = (mSize.x * 0.48f) / 2.0f; @@ -297,11 +302,11 @@ void DetailedGameListView::updateInfoPanel() mGenre.setVisible(false); mLblPlayers.setVisible(false); mPlayers.setVisible(false); - mBadges.setVisible(false); mLblLastPlayed.setVisible(false); mLastPlayed.setVisible(false); mLblPlayCount.setVisible(false); mPlayCount.setVisible(false); + mBadges.setVisible(false); } else { mLblRating.setVisible(true); @@ -316,11 +321,11 @@ void DetailedGameListView::updateInfoPanel() mGenre.setVisible(true); mLblPlayers.setVisible(true); mPlayers.setVisible(true); - mBadges.setVisible(true); mLblLastPlayed.setVisible(true); mLastPlayed.setVisible(true); mLblPlayCount.setVisible(true); mPlayCount.setVisible(true); + mBadges.setVisible(true); } bool fadingOut = false; @@ -443,6 +448,7 @@ void DetailedGameListView::updateInfoPanel() comps.push_back(&mImage); comps.push_back(&mDescription); comps.push_back(&mName); + comps.push_back(&mBadges); std::vector labels = getMDLabels(); comps.insert(comps.cend(), labels.cbegin(), labels.cend()); @@ -488,7 +494,6 @@ std::vector DetailedGameListView::getMDValues() ret.push_back(&mPublisher); ret.push_back(&mGenre); ret.push_back(&mPlayers); - ret.push_back(&mBadges); ret.push_back(&mLastPlayed); ret.push_back(&mPlayCount); return ret; diff --git a/es-app/src/views/gamelist/DetailedGameListView.h b/es-app/src/views/gamelist/DetailedGameListView.h index 356d86179..a6033b2f6 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.h +++ b/es-app/src/views/gamelist/DetailedGameListView.h @@ -47,7 +47,6 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; - BadgesComponent mBadges; RatingComponent mRating; DateTimeComponent mReleaseDate; TextComponent mDeveloper; @@ -57,6 +56,7 @@ private: DateTimeComponent mLastPlayed; TextComponent mPlayCount; TextComponent mName; + BadgesComponent mBadges; std::vector getMDLabels(); std::vector getMDValues(); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 3f5af0828..fe4770254 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -42,10 +42,10 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) , mPublisher(window) , mGenre(window) , mPlayers(window) - , mBadges(window) , mLastPlayed(window) , mPlayCount(window) , mName(window) + , mBadges(window) , mDescContainer(window) , mDescription(window) , mGamelistInfo(window) @@ -111,7 +111,6 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) mLblPlayers.setText("Players: "); addChild(&mLblPlayers); addChild(&mPlayers); - addChild(&mBadges); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); mLastPlayed.setDisplayRelative(true); @@ -120,6 +119,13 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) addChild(&mLblPlayCount); addChild(&mPlayCount); + // Badges. + addChild(&mBadges); + mBadges.setOrigin(0.0f, 0.0f); + mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); + mBadges.setSize(mSize.x * 0.15, mSize.y * 0.2f); + mBadges.setDefaultZIndex(50.0f); + mName.setPosition(mSize.x, mSize.y); mName.setDefaultZIndex(40.0f); mName.setColor(0xAAAAAAFF); @@ -165,6 +171,7 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE); mName.applyTheme(theme, getName(), "md_name", ALL); + mBadges.applyTheme(theme, getName(), "md_badges", ALL); initMDLabels(); std::vector labels = getMDLabels(); @@ -178,10 +185,10 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr& theme) initMDValues(); std::vector values = getMDValues(); - assert(values.size() == 9); - std::vector valElements = {"md_rating", "md_releasedate", "md_developer", - "md_publisher", "md_genre", "md_players", - "md_badges", "md_lastplayed", "md_playcount"}; + assert(values.size() == 8); + std::vector valElements = {"md_rating", "md_releasedate", "md_developer", + "md_publisher", "md_genre", "md_players", + "md_lastplayed", "md_playcount"}; for (unsigned int i = 0; i < values.size(); i++) values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); @@ -245,9 +252,6 @@ void VideoGameListView::initMDValues() mPublisher.setFont(defaultFont); mGenre.setFont(defaultFont); mPlayers.setFont(defaultFont); - - mBadges.setSize(defaultFont->getHeight() * 5.0f, static_cast(defaultFont->getHeight())); - mLastPlayed.setFont(defaultFont); mPlayCount.setFont(defaultFont); @@ -320,11 +324,11 @@ void VideoGameListView::updateInfoPanel() mGenre.setVisible(false); mLblPlayers.setVisible(false); mPlayers.setVisible(false); - mBadges.setVisible(false); mLblLastPlayed.setVisible(false); mLastPlayed.setVisible(false); mLblPlayCount.setVisible(false); mPlayCount.setVisible(false); + mBadges.setVisible(false); } else { mLblRating.setVisible(true); @@ -339,11 +343,11 @@ void VideoGameListView::updateInfoPanel() mGenre.setVisible(true); mLblPlayers.setVisible(true); mPlayers.setVisible(true); - mBadges.setVisible(true); mLblLastPlayed.setVisible(true); mLastPlayed.setVisible(true); mLblPlayCount.setVisible(true); mPlayCount.setVisible(true); + mBadges.setVisible(true); } bool fadingOut = false; @@ -484,6 +488,7 @@ void VideoGameListView::updateInfoPanel() comps.push_back(mVideo); comps.push_back(&mDescription); comps.push_back(&mName); + comps.push_back(&mBadges); std::vector labels = getMDLabels(); comps.insert(comps.cend(), labels.cbegin(), labels.cend()); @@ -526,7 +531,6 @@ std::vector VideoGameListView::getMDValues() ret.push_back(&mPublisher); ret.push_back(&mGenre); ret.push_back(&mPlayers); - ret.push_back(&mBadges); ret.push_back(&mLastPlayed); ret.push_back(&mPlayCount); return ret; diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index 3516d8477..f5cae00d0 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -57,10 +57,10 @@ private: TextComponent mPublisher; TextComponent mGenre; TextComponent mPlayers; - BadgesComponent mBadges; DateTimeComponent mLastPlayed; TextComponent mPlayCount; TextComponent mName; + BadgesComponent mBadges; std::vector getMDLabels(); std::vector getMDValues(); diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 0b35a30e3..339bd800c 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -150,10 +150,12 @@ std::map> The {{"pos", NORMALIZED_PAIR}, {"size", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, - {"direction", STRING}, - {"align", STRING}, - {"itemsPerLine", FLOAT}, - {"lines", FLOAT}, + {"rotation", FLOAT}, + {"rotationOrigin", NORMALIZED_PAIR}, + {"alignment", STRING}, + {"itemsPerRow", FLOAT}, + {"rows", FLOAT}, + {"itemPlacement", STRING}, {"itemMargin", NORMALIZED_PAIR}, {"slots", STRING}, {"customBadgeIcon", PATH}, diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index ef5844e33..091b1a3ed 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -54,7 +54,6 @@ namespace ThemeFlags Z_INDEX = 8192, ROTATION = 16384, VISIBLE = 32768, - DIRECTION = 65536, ALL = 0xFFFFFFFF }; } diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 6a16f8722..bce5eaeca 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -4,30 +4,31 @@ // BadgesComponent.cpp // // Game badges icons. -// Used by gamelist views. +// Used by the gamelist views. // #define SLOT_FAVORITE "favorite" #define SLOT_COMPLETED "completed" #define SLOT_KIDGAME "kidgame" #define SLOT_BROKEN "broken" -#define SLOT_ALTERNATIVE_EMULATOR "altemulator" +#define SLOT_ALTEMULATOR "altemulator" #include "components/BadgesComponent.h" +#include "Log.h" #include "ThemeData.h" #include "utils/StringUtil.h" BadgesComponent::BadgesComponent(Window* window) - : FlexboxComponent{window, mBadgeImages} - , mBadgeTypes{ - {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDGAME, SLOT_BROKEN, SLOT_ALTERNATIVE_EMULATOR}} + : GuiComponent{window} + , mFlexboxComponent{window, mBadgeImages} + , mBadgeTypes{{SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDGAME, SLOT_BROKEN, SLOT_ALTEMULATOR}} { mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; mBadgeIcons[SLOT_KIDGAME] = ":/graphics/badge_kidgame.svg"; mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; - mBadgeIcons[SLOT_ALTERNATIVE_EMULATOR] = ":/graphics/badge_altemulator.svg"; + mBadgeIcons[SLOT_ALTEMULATOR] = ":/graphics/badge_altemulator.svg"; } void BadgesComponent::setBadges(const std::vector& badges) @@ -52,12 +53,27 @@ void BadgesComponent::setBadges(const std::vector& badges) // Only recalculate the flexbox if any badges changed. for (auto& image : mBadgeImages) { if (prevVisibility[image.first] != image.second.isVisible()) { - onSizeChanged(); + mFlexboxComponent.onSizeChanged(); break; } } } +void BadgesComponent::render(const glm::mat4& parentTrans) +{ + if (!isVisible()) + return; + + if (mOpacity == 255) { + mFlexboxComponent.render(parentTrans); + } + else { + mFlexboxComponent.setOpacity(mOpacity); + mFlexboxComponent.render(parentTrans); + mFlexboxComponent.setOpacity(255); + } +} + void BadgesComponent::applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, @@ -69,6 +85,70 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (!elem) return; + if (elem->has("alignment")) { + const std::string alignment{elem->get("alignment")}; + if (alignment != "left" && alignment != "right") { + LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" + << alignment << "\""; + } + else { + mFlexboxComponent.setAlignment(alignment); + } + } + + if (elem->has("itemsPerRow")) { + const float itemsPerRow{elem->get("itemsPerRow")}; + if (itemsPerRow < 1.0f || itemsPerRow > 10.0f) { + LOG(LogWarning) + << "BadgesComponent: Invalid theme configuration, set to \"" + << itemsPerRow << "\""; + } + else { + mFlexboxComponent.setItemsPerLine(static_cast(itemsPerRow)); + } + } + + if (elem->has("rows")) { + const float rows{elem->get("rows")}; + if (rows < 1.0f || rows > 10.0f) { + LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" + << rows << "\""; + } + else { + mFlexboxComponent.setLines(static_cast(rows)); + } + } + + if (elem->has("itemPlacement")) { + std::string itemPlacement{elem->get("itemPlacement")}; + if (itemPlacement != "top" && itemPlacement != "center" && itemPlacement != "bottom" && + itemPlacement != "stretch") { + LOG(LogWarning) + << "BadgesComponent: Invalid theme configuration, set to \"" + << itemPlacement << "\""; + } + else { + if (itemPlacement == "top") + itemPlacement = "start"; + else if (itemPlacement == "bottom") + itemPlacement = "end"; + mFlexboxComponent.setItemPlacement(itemPlacement); + } + } + + if (elem->has("itemMargin")) { + const glm::vec2 itemMargin = elem->get("itemMargin"); + if (itemMargin.x < 0.0f || itemMargin.x > 100.0f || itemMargin.y < 0.0f || + itemMargin.y > 100.0f) { + LOG(LogWarning) + << "BadgesComponent: Invalid theme configuration, set to \"" + << itemMargin.x << "x" << itemMargin.y << "\""; + } + else { + mFlexboxComponent.setItemMargin(itemMargin); + } + } + if (elem->has("slots")) { std::vector slots = Utils::String::delimitedStringToVector( Utils::String::toLower(elem->get("slots")), " "); @@ -89,7 +169,15 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, } } - // Apply theme on the flexbox component parent. - FlexboxComponent::applyTheme(theme, view, element, properties); + GuiComponent::applyTheme(theme, view, element, properties); + + mFlexboxComponent.setPosition(mPosition); + mFlexboxComponent.setSize(mSize); + mFlexboxComponent.setOrigin(mOrigin); + mFlexboxComponent.setRotation(mRotation); + mFlexboxComponent.setRotationOrigin(mRotationOrigin); + mFlexboxComponent.setVisible(mVisible); + mFlexboxComponent.setDefaultZIndex(mDefaultZIndex); + mFlexboxComponent.setZIndex(mZIndex); } } diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 3b140b742..12cbd5927 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -4,15 +4,16 @@ // BadgesComponent.h // // Game badges icons. -// Used by gamelist views. +// Used by the gamelist views. // #ifndef ES_CORE_COMPONENTS_BADGES_COMPONENT_H #define ES_CORE_COMPONENTS_BADGES_COMPONENT_H #include "FlexboxComponent.h" +#include "GuiComponent.h" -class BadgesComponent : public FlexboxComponent +class BadgesComponent : public GuiComponent { public: BadgesComponent(Window* window); @@ -20,12 +21,17 @@ public: std::vector getBadgeTypes() { return mBadgeTypes; } void setBadges(const std::vector& badges); + void render(const glm::mat4& parentTrans) override; + void onSizeChanged() override { mFlexboxComponent.onSizeChanged(); } + virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; private: + FlexboxComponent mFlexboxComponent; + std::vector mBadgeTypes; std::map mBadgeIcons; std::vector> mBadgeImages; diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 95d41920f..4d921e47f 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -4,28 +4,30 @@ // FlexboxComponent.cpp // // Flexbox layout component. -// Used by gamelist views. // -#define DEFAULT_DIRECTION Direction::row -#define DEFAULT_ALIGN Align::center +#define DEFAULT_DIRECTION "row" +#define DEFAULT_ALIGNMENT "left" #define DEFAULT_ITEMS_PER_LINE 4 -#define DEFAULT_LINES 1 +#define DEFAULT_LINES 2 +#define DEFAULT_ITEM_PLACEMENT "center" #define DEFAULT_MARGIN_X 10.0f #define DEFAULT_MARGIN_Y 10.0f #include "components/FlexboxComponent.h" +#include "Settings.h" #include "ThemeData.h" FlexboxComponent::FlexboxComponent(Window* window, std::vector>& images) : GuiComponent{window} - , mDirection{DEFAULT_DIRECTION} - , mAlign{DEFAULT_ALIGN} , mImages(images) + , mDirection{DEFAULT_DIRECTION} + , mAlignment{DEFAULT_ALIGNMENT} , mItemsPerLine{DEFAULT_ITEMS_PER_LINE} , mLines{DEFAULT_LINES} + , mItemPlacement{DEFAULT_ITEM_PLACEMENT} , mItemMargin{glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}} , mLayoutValid{false} { @@ -36,27 +38,30 @@ void FlexboxComponent::computeLayout() // Start placing items in the top-left. float anchorX{0.0f}; float anchorY{0.0f}; - float anchorOriginX{0.0f}; - float anchorOriginY{0.0f}; // Translation directions when placing items. - glm::ivec2 directionLine{1, 0}; - glm::ivec2 directionRow{0, 1}; + glm::vec2 directionLine{1, 0}; + glm::vec2 directionRow{0, 1}; // Change direction. - if (mDirection == Direction::column) { + if (mDirection == "column") { directionLine = {0, 1}; directionRow = {1, 0}; } // Compute maximum image dimensions. glm::vec2 grid; - if (mDirection == Direction::row) + if (mDirection == "row") grid = {mItemsPerLine, mLines}; else grid = {mLines, mItemsPerLine}; glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid}; + if (grid.x * grid.y < static_cast(mImages.size())) { + LOG(LogWarning) << "FlexboxComponent: Invalid theme configuration, the number of badges " + "exceeds the product of times "; + } + // Set final image dimensions. for (auto& image : mImages) { if (!image.second.isVisible()) @@ -77,8 +82,6 @@ void FlexboxComponent::computeLayout() } // Pre-compute layout parameters. - float lineWidth = (mDirection == Direction::row ? (maxItemSize.y + mItemMargin.y) : - (maxItemSize.x + mItemMargin.x)); float anchorXStart{anchorX}; float anchorYStart{anchorY}; @@ -92,30 +95,29 @@ void FlexboxComponent::computeLayout() auto size{image.second.getSize()}; // Top-left anchor position. - float x{anchorX - anchorOriginX * size.x}; - float y{anchorY - anchorOriginY * size.y}; + float x{anchorX}; + float y{anchorY}; - // Apply alignment - if (mAlign == Align::end) { + // Apply alignment. + if (mItemPlacement == "end") { x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) : 0; } - else if (mAlign == Align::center) { + else if (mItemPlacement == "center") { x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0; y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; } - else if (mAlign == Align::stretch && mDirection == Direction::row) { + else if (mItemPlacement == "stretch" && mDirection == "row") { image.second.setSize(image.second.getSize().x, maxItemSize.y); } - // Apply origin. - if (mOrigin.x > 0 && mOrigin.x <= 1) - x -= mOrigin.x * mSize.x; - if (mOrigin.y > 0 && mOrigin.y <= 1) - y -= mOrigin.y * mSize.y; + // TODO: Doesn't work correctly. + // Apply overall container alignment. + if (mAlignment == "right") + x += (mSize.x - size.x * grid.x) - mItemMargin.x; // Store final item position. - image.second.setPosition(getPosition().x + x, getPosition().y + y); + image.second.setPosition(x, y); // Translate anchor. if ((i++ + 1) % std::max(1, static_cast(mItemsPerLine)) != 0) { @@ -126,11 +128,11 @@ void FlexboxComponent::computeLayout() else { // Translate to first position of next line. if (directionRow.x == 0) { - anchorY += lineWidth * static_cast(directionRow.y); + anchorY += size.y + mItemMargin.y; anchorX = anchorXStart; } else { - anchorX += lineWidth * static_cast(directionRow.x); + anchorX += size.x + mItemMargin.x; anchorY = anchorYStart; } } @@ -147,47 +149,20 @@ void FlexboxComponent::render(const glm::mat4& parentTrans) if (!mLayoutValid) computeLayout(); - for (auto& image : mImages) - image.second.render(parentTrans); -} + glm::mat4 trans{parentTrans * getTransform()}; + Renderer::setMatrix(trans); -void FlexboxComponent::applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, - unsigned int properties) -{ - using namespace ThemeFlags; + if (Settings::getInstance()->getBool("DebugImage")) + Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); - glm::vec2 scale{getParent() ? getParent()->getSize() : - glm::vec2{static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}}; - - const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); - if (!elem) - return; - - if (properties & DIRECTION && elem->has("direction")) - mDirection = - elem->get("direction") == "row" ? Direction::row : Direction::column; - - if (elem->has("align")) { - const auto a = elem->get("align"); - mAlign = (a == "start" ? - Align::start : - (a == "end" ? Align::end : (a == "center" ? Align::center : Align::stretch))); + for (auto& image : mImages) { + if (mOpacity == 255) { + image.second.render(trans); + } + else { + image.second.setOpacity(mOpacity); + image.second.render(trans); + image.second.setOpacity(255); + } } - - if (elem->has("itemsPerLine")) - mItemsPerLine = elem->get("itemsPerLine"); - - if (elem->has("lines")) - mLines = elem->get("lines"); - - if (elem->has("itemMargin")) - mItemMargin = elem->get("itemMargin") * scale; - - GuiComponent::applyTheme(theme, view, element, properties); - - // Layout no longer valid. - mLayoutValid = false; } diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index c278b1aed..bbe385aba 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -4,7 +4,6 @@ // FlexboxComponent.h // // Flexbox layout component. -// Used by gamelist views. // #ifndef ES_CORE_COMPONENTS_FLEXBOX_COMPONENT_H @@ -16,26 +15,21 @@ class FlexboxComponent : public GuiComponent { public: - enum class Direction : char { - row, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). - column - }; - - enum class Align : char { - start, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). - end, - center, - stretch - }; - explicit FlexboxComponent(Window* window, std::vector>& images); - // Getters/Setters for rendering options. - Align getAlign() const { return mAlign; } - void setAlign(Align value) + // Getters/setters for the layout. + void setDirection(const std::string& direction) { - mAlign = value; + assert(direction == "row" || direction == "column"); + mDirection = direction; + } + + std::string getAlignment() const { return mAlignment; } + void setAlignment(const std::string& value) + { + assert(value == "left" || value == "right"); + mAlignment = value; mLayoutValid = false; } @@ -53,34 +47,39 @@ public: mLayoutValid = false; } + std::string getItemPlacement() const { return mItemPlacement; } + void setItemPlacement(const std::string& value) + { + assert(value == "start" || value == "center" || value == "end" || value == "stretch"); + mItemPlacement = value; + mLayoutValid = false; + } + glm::vec2 getItemMargin() const { return mItemMargin; } void setItemMargin(glm::vec2 value) { - mItemMargin = value; + mItemMargin.x = std::roundf(value.x * Renderer::getScreenWidth()); + mItemMargin.y = std::roundf(value.y * Renderer::getScreenHeight()); mLayoutValid = false; } void onSizeChanged() override { mLayoutValid = false; } void render(const glm::mat4& parentTrans) override; - void applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, - unsigned int properties) override; private: // Calculate flexbox layout. void computeLayout(); - // Layout options. - Direction mDirection; - Align mAlign; - std::vector>& mImages; + // Layout options. + std::string mDirection; + std::string mAlignment; unsigned int mItemsPerLine; unsigned int mLines; - + std::string mItemPlacement; glm::vec2 mItemMargin; + bool mLayoutValid; }; From 47242760182e7ac0c56c4bc6b4074404af66066b Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 22:55:44 +0200 Subject: [PATCH 047/128] (rbsimple-DE) Update to align with the changes to BadgesComponent. --- themes/rbsimple-DE/theme.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index bda55e899..c976a96fc 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -240,10 +240,9 @@ based on: 'recalbox-multi' by the Recalbox community 0.8125 0.675 0.15 0.21 0 0 - row - start - 3 - 2 + left + 3 + 2 0.0028125 0.005 favorite completed kidgame broken altemulator From cb2b26657beb23a989309b4270b709a98c5988f6 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 22:58:30 +0200 Subject: [PATCH 048/128] Updated the badges documentation. --- THEMES-DEV.md | 291 +++++++++++++++++++++++++------------------------- 1 file changed, 144 insertions(+), 147 deletions(-) diff --git a/THEMES-DEV.md b/THEMES-DEV.md index fc6c82f57..b2a659cf0 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -1,83 +1,90 @@ # EmulationStation Desktop Edition (ES-DE) v1.2 (development version) - Themes -**Note:** If creating theme sets specifically for ES-DE, please add `-DE` to the theme name, as in `rbsimple-DE`. Because the ES-DE theme support has already deviated somehow from the RetroPie EmulationStation fork and will continue to deviate further in the future, the theme set will likely not be backwards compatible. It would be confusing and annoying for a user that downloads and attempts to use an ES-DE theme set in another EmulationStation fork only to get crashes, error messages or corrupted graphics. At least the -DE extension is a visual indicator that it's an ES-DE specific theme set. +**Note:** This document is only relevant for the current ES-DE development version, if you would like to see the documentation for the latest stable release, refer to [THEMES.md](THEMES.md) instead. -Also note that this document is only relevant for the current ES-DE development version, if you would like to see the documentation for the latest stable release, refer to [THEMES.md](THEMES.md) instead. +If creating theme sets specifically for ES-DE, please add `-DE` to the theme name, as in `rbsimple-DE`. Because the ES-DE theme support has already deviated somehow from the RetroPie EmulationStation fork and will continue to deviate further in the future, the theme set will likely not be backwards compatible. It would be confusing and annoying for a user that downloads and attempts to use an ES-DE theme set in another EmulationStation fork only to get crashes, error messages or corrupted graphics. At least the -DE extension is a visual indicator that it's an ES-DE specific theme set. -ES-DE allows each system to have its own "theme." A theme is a collection **views** that define some **elements**, each with their own **properties**. +Table of contents: -The first place ES-DE will check for a theme is in the system's `` folder, for a theme.xml file: -* `[SYSTEM_PATH]/theme.xml` +[[_TOC_]] -If that file doesn't exist, ES-DE will try to find the theme in the current **theme set**. Theme sets are just a collection of individual system themes arranged in the "themes" folder under some name. A theme set can provide a default theme that will be used if there is no matching system theme. Here's an example: +## Introduction + +ES-DE allows the grouping of themes for multiple game systems into a **theme set**. Each theme is a collection of **views** that define some **elements**, each with their own **properties**. + +Every game system has its own subdirectory within the theme set directory structure, and these are defined in the systems configuration file `es_systems.xml` either via the optional `` tag, or otherwise via the mandatory `` tag. When ES-DE populates a system on startup it will look for a file named `theme.xml` in each such directory. + +By placing a theme.xml file directly in the root of the theme set directory, that file will be processed as a default if there is no game-specific theme.xml file available. + +In the example below, we have a theme set named `mythemeset-DE` which includes the `snes` and `nes` systems. Assuming you have some games installed for these systems, the files `mythemeset-DE/nes/theme.xml` and `mythemeset-DE/snes/theme.xml` will be processed on startup. If there are no games available for a system, its theme.xml file will be skipped. + +The directory structure of our example theme set could look something like the following: ``` ... themes/ - my_theme_set/ - snes/ - theme.xml - my_cool_background.jpg + mythemeset-DE/ + core/ + font.ttf + bold_font.ttf + frame.png nes/ theme.xml - my_other_super_cool_background.jpg + background.jpg + logo.svg + logo_video.svg - common_resources/ - my_font.ttf - - theme.xml (Default theme) - another_theme_set/ snes/ theme.xml - some_resource.svg + background.jpg + logo.svg + logo_video.svg + + fonts.xml + theme.xml ``` -The theme set system makes it easy for users to try different themes and allows distributions to include multiple theme options. Users can change the currently active theme set in the "UI Settings" menu. The option is only visible if at least one theme set exists. +The theme set approach makes it easy for users to install different themes and choose between them from the _UI Settings_ menu. -There are two places ES-DE can load theme sets from: -* `[HOME]/.emulationstation/themes/[CURRENT_THEME_SET]/[SYSTEM_THEME]/theme.xml` -* `[INSTALLATION PATH]/themes/[CURRENT_THEME_SET]/[SYSTEM_THEME]/theme.xml` +There are two places that ES-DE can load theme sets from: +* `[HOME]/.emulationstation/themes/[THEME_SET]/` +* `[INSTALLATION PATH]/themes/[THEME_SET]/` An example installation path would be: \ -`/usr/share/emulationstation/themes/[CURRENT_THEME_SET]/[SYSTEM_THEME]/theme.xml` +`/usr/share/emulationstation/themes/rbsimple-DE/` -`[SYSTEM_THEME]` is the `` tag for the system, as defined in `es_systems.cfg`. If the `` tag is not set, ES-DE will use the system's ``. +If a theme set with the same name exists in both locations, the one in the home directory will be loaded and the other one will be skipped. -If both files happen to exist, ES-DE will pick the first one (the one located in the home directory). +## Simple example -Again, the `[CURRENT_THEME_SET]` value is set in the "UI Settings" menu. If it has not been set yet or the previously selected theme set is missing, the first available theme set will be used as the default. - -# Simple Example - -Here is a very simple theme that changes the description text's color: +Here is a very simple theme that changes the color of the game description text: ```xml - 7 00FF00 - + 0.5 0.5 0.5 0.5 0.8 0.8 - ./my_art/my_awesome_image.jpg + ./core/frame.png ``` -# How it works +## How it works -Everything must be inside a `` tag. +All configuration must be contained within a `` tag pair. -**The `` tag *must* be specified**. This is the version of the theming system the theme was designed for. +The `` tag **must** be specified. This is the version of the theming system the theme was designed for. The current version is 7. -A *view* can be thought of as a particular "screen" within EmulationStation. Views are defined like this: +A _view_ can be thought of as a particular "screen" within ES-DE. Views are defined like this: ```xml @@ -85,63 +92,61 @@ A *view* can be thought of as a particular "screen" within EmulationStation. Vie ``` - - -An *element* is a particular visual element, such as an image or a piece of text. You can either modify an element that already exists for a particular view (as is done in the "description" example), like this: +An *element* is a particular visual element, such as an image or a piece of text. You can modify an element that already exists for a particular view, as was done for the "description" example: ```xml - - ... define properties here ... - + + ... define properties here ... + ``` -Or, you can create your own elements by adding `extra="true"` (as is done in the "my_image" example) like this: +Or you can create your own elements by adding `extra="true"`, as was done for the "frame" example: ```xml - - ... define properties here ... - + + ... define properties here ... + ``` -"Extra" elements will be drawn in the order they are defined (so define backgrounds first!). When they get drawn relative to the pre-existing elements depends on the view. Make sure "extra" element names do not clash with existing element names! An easy way to protect against this is to just start all your extra element names with some prefix like "e_". +"Extra" elements will be drawn in the order they are defined (so make sure to define backgrounds first). In what order they get drawn relative to the pre-existing elements depends on the view. Make sure "extra" element names do not clash with existing element names. An easy way to protect against this is to start all your extra element names with a prefix such as "e_". -*Properties* control how a particular *element* looks - for example, its position, size, image path, etc. The type of the property determines what kinds of values you can use. You can read about the types below in the "Reference" section. Properties are defined like this: +*Properties* control how a particular *element* looks - for example its position, size, image path etc. The type of the property determines what kinds of values you can use. You can read about the types below in the "Reference" section. Properties are defined like this: ```xml - ValueHere +ValueHere ``` -# Advanced Features +## Advanced features -It is recommended that if you are writing a theme you launch EmulationStation with the `--debug` and `--windowed` switches. This way you can read error messages without having to check the log file. You can also reload the current gamelist view and system view with `Ctrl-R` if `--debug` is specified. +If you are writing a theme it's recommended to launch ES-DE with the `--debug` flag from a terminal window. If on Unix you can also pass the `--windowed` and `--resolution` flags to avoid having the application window fill the entire screen. On macOS and Windows you only need to pass the `--resolution` flag to accomplish this. By doing so, you can read error messages directly in the terminal window without having to open the log file. You can also reload the current gamelist view and system view with `Ctrl-R` if the `--debug` flag has been set. -### The `` tag +### The \ tag -You can include theme files within theme files, similar to `#include` in C (though the internal mechanism is different, the effect is the same). Example: +You can include theme files within theme files, for example: -`~/.emulationstation/all_themes.xml`: +`~/.emulationstation/themes/mythemeset-DE/fonts.xml`: ```xml - 7 - ./all_themes/myfont.ttf + ./core/font.ttf + 0.035 00FF00 ``` -`~/.emulationstation/snes/theme.xml`: +`~/.emulationstation/themes/mythemeset-DE/snes/theme.xml`: ```xml 7 - ./../all_themes.xml + ./../fonts.xml FF0000 @@ -150,84 +155,77 @@ You can include theme files within theme files, similar to `#include` in C (thou ``` -Is equivalent to this `snes/theme.xml`: +The above is equivalent to the following: ```xml 7 - ./all_themes/myfont.ttf + ./core/font.ttf + 0.035 FF0000 ``` -Notice that properties that were not specified got merged (``) and the `snes/theme.xml` could overwrite the included files' values (``). Also notice the included file still needed the `` tag. +Note that properties can get merged. In the above example the `` tag from `fonts.xml` got overwritten by the equivalent tag in `snes/theme.xml`. This happens because that tag was effectively declared after the first `` tag. Be aware that the included file also needs the `` tag. ### Theming multiple views simultaneously -Sometimes you want to apply the same properties to the same elements across multiple views. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas). So, for example, to easily apply the same header to the basic, grid, and system views: +Sometimes you want to apply the same properties to the same elements across multiple views. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas). So for example, to apply the same logo to the basic and detailed views you could write the following: ```xml - 7 - + - ./snes_art/snes_header.png + ./snes/logo.svg - + - ./snes_art/snes_header_detailed.png + ./snes/logo_video.svg ``` -This is equivalent to: -```xml +The above is equivalent to: +```xml 7 - ./snes_art/snes_header.png + ./snes/logo.svg - ./snes_art/snes_header_detailed.png + ./snes/logo.svg - + - ./snes_art/snes_header.png + ./snes/logo_video.svg - - - ./snes_art/snes_header.png - - - ... and any other view that might try to look up "logo" ... ``` ### Theming multiple elements simultaneously -You can theme multiple elements *of the same type* simultaneously. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas), just like it does for views, as long as the elements have the same type. This is useful if you want to, say, apply the same color to all the metadata labels: +You can theme multiple elements *of the same type* simultaneously. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas). This is useful if you want to, say, apply the same color to all the metadata labels: ```xml - 7 - + 48474D @@ -238,7 +236,6 @@ You can theme multiple elements *of the same type* simultaneously. The `name` a Which is equivalent to: ```xml - 7 @@ -270,20 +267,16 @@ Which is equivalent to: ``` -Just remember, *this only works if the elements have the same type!* - -### Element rendering order with z-index - -You can now change the order in which elements are rendered by setting `zIndex` values. Default values correspond to the default rendering order while allowing elements to easily be shifted without having to set `zIndex` values for every element. Elements will be rendered in order from smallest z-index to largest. +Just remember, _this only works if the elements have the same type._ -#### Navigation sounds +### Navigation sounds -The navigation sounds are configured globally per theme set, so it needs to be defined as a feature and with the view set to the special 'all' category. +Navigation sounds are configured globally per theme set, so it needs to be defined as a feature and with the view set to the special "all" category. It's recommended to put these elements in a separate file and include it from the main theme file (e.g. `./navigationsounds.xml`). There are seven different navigation sounds that can be configured. The names as well as the element structure should be self-explanatory based on the example below. -Starting EmulationStation with the --debug flag will provide feedback on whether any navigation sound elements were read from the theme set. If no navigation sound is provided by the theme, ES-DE will use the bundled navigation sound file as a fallback. This is done per sound, so the theme could provide for example one or two custom sound files while using the bundled ES-DE sounds for the other samples. +Starting ES-DE with the --debug flag will provide feedback on whether any navigation sound elements were read from the theme set. If no navigation sounds are provided by the theme, ES-DE will use the bundled navigation sounds as a fallback. This is done per sound file, so the theme could provide for example one or two custom sounds while using the bundled ES-DE sounds for the rest. Example debug output: ``` @@ -297,7 +290,6 @@ Jul 12 11:28:58 Debug: Sound::getFromTheme(): Tag not found, using fallback sou Example `navigationsounds.xml`, to be included from the main theme file: ```xml - 7 @@ -328,14 +320,18 @@ Example `navigationsounds.xml`, to be included from the main theme file: ``` -#### Defaults +### Element rendering order using zIndex -##### system +You can change the order in which elements are rendered by setting their `zIndex` values. All elements have a default value so you only need to define it for the ones you wish to explicitly change. Elements will be rendered in order from smallest to largest values. + +Below are the default zIndex values per element type: + +#### system * Extra Elements `extra="true"` - 10 * `carousel name="systemcarousel"` - 40 * `text name="systemInfo"` - 50 -##### basic, detailed, grid, video +#### basic, detailed, video, grid * `image name="background"` - 0 * Extra Elements `extra="true"` - 10 * `textlist name="gamelist"` - 20 @@ -370,21 +366,23 @@ Example `navigationsounds.xml`, to be included from the main theme file: * `image name="logo"` * Gamelist information - 50 * `text name="gamelistInfo"` +* Badges - 50 + * `badges name="md_badges"` ### Theme variables Theme variables can be used to simplify theme construction. There are 2 types of variables available. -* System Variables -* Theme Defined Variables +* System variables +* Theme defined variables -#### System Variables +#### System variables -System variables are system specific and are derived from the values in es_systems.cfg. +System variables are system specific and are derived from the values in es_systems.xml. * `system.name` * `system.fullName` * `system.theme` -#### Theme Defined Variables +#### Theme defined variables Variables can also be defined in the theme. ``` @@ -406,9 +404,23 @@ or to specify only a portion of the value of a theme property: ```` -# Reference +## Reference + +### Views, their elements, and themeable properties + +#### system +* `helpsystem name="help"` - ALL + - The help system style for this view. +* `carousel name="systemcarousel"` -ALL + - The system logo carousel +* `image name="logo"` - PATH | COLOR + - A logo image, to be displayed in the system logo carousel. +* `text name="logoText"` - FONT_PATH | COLOR | FORCE_UPPERCASE | LINE_SPACING | TEXT + - A logo text, to be displayed system name in the system logo carousel when no logo is available. +* `text name="systemInfo"` - ALL + - Displays details of the system currently selected in the carousel. +* You can use extra elements (elements with `extra="true"`) to add your own backgrounds, etc. They will be displayed behind the carousel, and scroll relative to the carousel. -## Views, their elements, and themeable properties: #### basic * `helpsystem name="help"` - ALL @@ -422,7 +434,6 @@ or to specify only a portion of the value of a theme property: * `textlist name="gamelist"` - ALL - The gamelist. `primaryColor` is for games, `secondaryColor` is for folders. Centered by default. ---- #### detailed * `helpsystem name="help"` - ALL @@ -438,8 +449,8 @@ or to specify only a portion of the value of a theme property: * `text name="gamelistInfo"` - ALL - Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. Left aligned by default. -* Metadata - * Labels +- Metadata + - Labels * `text name="md_lbl_rating"` - ALL * `text name="md_lbl_releasedate"` - ALL * `text name="md_lbl_developer"` - ALL @@ -450,8 +461,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_lbl_playcount"` - ALL * Values - * All values will follow to the right of their labels if a position isn't specified. - + - _All values will follow to the right of their labels if a position isn't specified._ * `image name="md_image"` - POSITION | SIZE | Z_INDEX - Path is the "image" metadata for the currently selected game. * `rating name="md_rating"` - ALL @@ -467,7 +477,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). * `badges name="md_badges"` - ALL - - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. + - The "badges" metadata. Displayed as a group of badges that indicate metadata such as favorites and completed games. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -477,6 +487,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_name"` - ALL - The "name" metadata (the game name). Unlike the others metadata fields, the name is positioned offscreen by default + #### video * `helpsystem name="help"` - ALL - The help system style for this view. @@ -491,8 +502,8 @@ or to specify only a portion of the value of a theme property: * `text name="gamelistInfo"` - ALL - Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. Left aligned by default. -* Metadata - * Labels +- Metadata + - Labels * `text name="md_lbl_rating"` - ALL * `text name="md_lbl_releasedate"` - ALL * `text name="md_lbl_developer"` - ALL @@ -503,8 +514,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_lbl_playcount"` - ALL * Values - * All values will follow to the right of their labels if a position isn't specified. - + - _All values will follow to the right of their labels if a position isn't specified._ * `image name="md_image"` - POSITION | SIZE | Z_INDEX - Path is the "image" metadata for the currently selected game. * `image name="md_marquee"` - POSITION | SIZE | Z_INDEX @@ -524,7 +534,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). * `badges name="md_badges"` - ALL - - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. + - The "badges" metadata. Displayed as a group of badges that indicate metadata such as favorites and completed games. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -534,7 +544,6 @@ or to specify only a portion of the value of a theme property: * `text name="md_name"` - ALL - The "name" metadata (the game name). Unlike the others metadata fields, the name is positioned offscreen by default ---- #### grid * `helpsystem name="help"` - ALL @@ -554,8 +563,8 @@ or to specify only a portion of the value of a theme property: * `text name="gamelistInfo"` - ALL - Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. Left aligned by default. -* Metadata - * Labels +- Metadata + - Labels * `text name="md_lbl_rating"` - ALL * `text name="md_lbl_releasedate"` - ALL * `text name="md_lbl_developer"` - ALL @@ -566,8 +575,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_lbl_playcount"` - ALL * Values - * All values will follow to the right of their labels if a position isn't specified. - + - _All values will follow to the right of their labels if a position isn't specified._ * `rating name="md_rating"` - ALL - The "rating" metadata. * `datetime name="md_releasedate"` - ALL @@ -581,7 +589,7 @@ or to specify only a portion of the value of a theme property: * `text name="md_players"` - ALL - The "players" metadata (number of players the game supports). * `badges name="md_badges"` - ALL - - The "badges" metadata. Displayed as a group of badges that indicate boolean metadata such as favorite, broken. + - The "badges" metadata. Displayed as a group of badges that indicate metadata such as favorites and completed games. * `datetime name="md_lastplayed"` - ALL - The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago"). * `text name="md_playcount"` - ALL @@ -591,23 +599,8 @@ or to specify only a portion of the value of a theme property: * `text name="md_name"` - ALL - The "name" metadata (the game name). Unlike the others metadata fields, the name is positioned offscreen by default ---- -#### system -* `helpsystem name="help"` - ALL - - The help system style for this view. -* `carousel name="systemcarousel"` -ALL - - The system logo carousel -* `image name="logo"` - PATH | COLOR - - A logo image, to be displayed in the system logo carousel. -* `text name="logoText"` - FONT_PATH | COLOR | FORCE_UPPERCASE | LINE_SPACING | TEXT - - A logo text, to be displayed system name in the system logo carousel when no logo is available. -* `text name="systemInfo"` - ALL - - Displays details of the system currently selected in the carousel. -* You can use extra elements (elements with `extra="true"`) to add your own backgrounds, etc. They will be displayed behind the carousel, and scroll relative to the carousel. - - -## Types of properties: +### Types of properties * NORMALIZED_PAIR - two decimals, in the range [0..1], delimited by a space. For example, `0.25 0.5`. Most commonly used for position (x and y coordinates) and size (width and height). * NORMALIZED_RECT - four decimals, in the range [0..1], delimited by a space. For example, `0.25 0.5 0.10 0.30`. Most commonly used for padding to store top, left, bottom and right coordinates. @@ -618,7 +611,7 @@ or to specify only a portion of the value of a theme property: * STRING - a string of text. -## Types of elements and their properties: +### Types of elements and their properties Common to almost all elements is a `pos` and `size` property of the NORMALIZED_PAIR type. They are normalized in terms of their "parent" object's size; 99% of the time, this is just the size of the screen. In this case, `0 0` would correspond to the top left corner, and `1 1` the bottom right corner (a positive Y value points further down). `pos` almost always refers to the top left corner of your element. You *can* use numbers outside of the [0..1] range if you want to place an element partially or completely off-screen. @@ -803,7 +796,7 @@ Can be created as an extra. * `zIndex` - type: FLOAT. - z-index value for component. Components will be rendered in order of z-index value from low to high. -EmulationStation borrows the concept of "nine patches" from Android (or "9-Slices"). Currently the implementation is very simple and hard-coded to only use 48x48px images (16x16px for each "patch"). Check the `data/resources` directory for some examples (button.png, frame.png). +ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Currently the implementation is very simple and hard-coded to only use 48x48px images (16x16px for each "patch"). Check the `data/resources` directory for some examples (button.png, frame.png). #### rating @@ -923,19 +916,21 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice - `w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. * `origin` - type: NORMALIZED_PAIR. - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the component exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied. -* `direction` - type: STRING. - - Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. -* `align` - type: STRING. - - Valid values are "start", "center", "end", or "stretch". Controls orthogonal alignment to the line axis. "stretch" will stretch the badges to fill-up the line width. -* `itemsPerLine` - type: FLOAT. - - Number of badges that fit on a line. When more badges are available a new line will be started. -* `lines` - type: FLOAT. - - The number of lines available. +* `rotation` - type: FLOAT. + - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. +* `rotationOrigin` - type: NORMALIZED_PAIR. + - Point around which the image will be rotated. Defaults to `0.5 0.5`. +* `itemsPerRow` - type: FLOAT. + - Number of badges that fit on a row. When more badges are available a new row will be started. +* `rows` - type: FLOAT. + - The number of rows available. +* `itemPlacement` - type: STRING. + - Valid values are "top", "center", "bottom", or "stretch". Controls vertical alignment of each badge if images of different heights are used. "Stretch" will stretch the badge to the full height. * `itemMargin` - type: NORMALIZED_PAIR. - The margins between badges. Possible combinations: - `x y` - horizontal and vertical margins. * `slots` - type: STRING. - - The badge types that should be displayed. Should be specified as a dist of strings separated by spaces. The order will be followed when placing badges on the screen. + - The badge types that should be displayed. Should be specified as a list of strings separated by spaces. The order will be followed when placing badges on the screen. - Available badges are: - "favorite": Will be shown when the game is marked as favorite. - "completed": Will be shown when the game is marked as completed. @@ -992,7 +987,9 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu. -To see some examples of EmulationStation themes, the following resources are recommended: +## Example theme sets + +To see some example EmulationStation themes, the following resources are recommended: https://aloshi.com/emulationstation#themes From 8ec17dbaee667baa960823bf409dda0f8a7e926d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 23:27:48 +0200 Subject: [PATCH 049/128] Fixed a very minor line break issue. --- es-app/src/guis/GuiScreensaverOptions.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/es-app/src/guis/GuiScreensaverOptions.cpp b/es-app/src/guis/GuiScreensaverOptions.cpp index 8c7dd71ca..ea6eaf037 100644 --- a/es-app/src/guis/GuiScreensaverOptions.cpp +++ b/es-app/src/guis/GuiScreensaverOptions.cpp @@ -52,7 +52,8 @@ GuiScreensaverOptions::GuiScreensaverOptions(Window* window, const std::string& // If before it wasn't risky but now there's a risk of problems, show warning. mWindow->pushGui(new GuiMsgBox( mWindow, getHelpStyle(), - "THE 'VIDEO' SCREENSAVER SHOWS\nVIDEOS FROM YOUR GAMELISTS\n\n" + "THE 'VIDEO' SCREENSAVER SHOWS\n" + "VIDEOS FROM YOUR GAMELISTS\n\n" "IF YOU DO NOT HAVE ANY VIDEOS, THE\n" "SCREENSAVER WILL DEFAULT TO 'DIM'", "OK", [] { return; }, "", nullptr, "", nullptr)); From b9b4bd120d357a5e27339a5a07b60ca37c5b3fc4 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 23:32:26 +0200 Subject: [PATCH 050/128] Fixed multiple issues where ComponentGrid would display incorrect help prompts. --- es-core/src/components/ComponentGrid.cpp | 44 +++++++++++++++++------- es-core/src/guis/GuiMsgBox.cpp | 5 --- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/es-core/src/components/ComponentGrid.cpp b/es-core/src/components/ComponentGrid.cpp index fd87cd10d..1fd3c4c7e 100644 --- a/es-core/src/components/ComponentGrid.cpp +++ b/es-core/src/components/ComponentGrid.cpp @@ -474,22 +474,42 @@ std::vector ComponentGrid::getHelpPrompts() if (e) prompts = e->component->getHelpPrompts(); - bool canScrollVert = mGridSize.y > 1; - bool canScrollHoriz = mGridSize.x > 1; - for (auto it = prompts.cbegin(); it != prompts.cend(); it++) { - if (it->first == "up/down/left/right") { - canScrollHoriz = false; - canScrollVert = false; - break; + bool canScrollVert = false; + + // If the currently selected cell does not fill the entire Y axis, then check if the cells + // above or below are actually focusable as otherwise they should not affect the help prompts. + if (mGridSize.y > 1 && e->dim.y < mGridSize.y) { + if (e->pos.y - e->dim.y >= 0) { + const GridEntry* cell = getCellAt(glm::ivec2{e->pos.x, e->pos.y - e->dim.y}); + if (cell != nullptr && cell->canFocus) + canScrollVert = true; } - else if (it->first == "up/down") { - canScrollVert = false; - } - else if (it->first == "left/right") { - canScrollHoriz = false; + if (e->pos.y + e->dim.y < mGridSize.y) { + const GridEntry* cell = getCellAt(glm::ivec2{e->pos.x, e->pos.y + e->dim.y}); + if (cell != nullptr && cell->canFocus) + canScrollVert = true; } } + // There is currently no situation in the application where unfocusable cells are located + // next to each other horizontally, so this code is good enough. If this changes in the + // future, code similar to the the vertical cell handling above needs to be added. + bool canScrollHoriz = (mGridSize.x > 1 && e->dim.x < mGridSize.x); + + // Check existing capabilities as indicated by the help prompts, and if the prompts should + // be combined into "up/down/left/right" then also remove the single-axis prompts. + if (!prompts.empty() && prompts.back() == HelpPrompt("up/down", "choose")) { + canScrollVert = true; + if (canScrollHoriz && canScrollVert) + prompts.pop_back(); + } + else if (!prompts.empty() && prompts.back() == HelpPrompt("left/right", "choose")) { + canScrollHoriz = true; + if (canScrollHoriz && canScrollVert) + prompts.pop_back(); + } + + // Any duplicates will be removed in Window::setHelpPrompts() if (canScrollHoriz && canScrollVert) prompts.push_back(HelpPrompt("up/down/left/right", "choose")); else if (canScrollHoriz) diff --git a/es-core/src/guis/GuiMsgBox.cpp b/es-core/src/guis/GuiMsgBox.cpp index 0e0a7fcb2..d92acbb9b 100644 --- a/es-core/src/guis/GuiMsgBox.cpp +++ b/es-core/src/guis/GuiMsgBox.cpp @@ -179,11 +179,6 @@ std::vector GuiMsgBox::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); - // If there is only one button, then remove the "Choose" help symbol - // as there is no way to make a choice. - if (mButtons.size() == 1) - prompts.pop_back(); - if (!mDisableBackButton) prompts.push_back(HelpPrompt("b", "Back")); From 5b2725ba76104c3ca2e613d58327a9f99146f961 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 12 Oct 2021 23:40:58 +0200 Subject: [PATCH 051/128] Documentation update. --- CHANGELOG.md | 9 +++++++++ CONTRIBUTING.md | 8 ++++---- USERGUIDE-DEV.md | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3af834783..5d7e306b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,10 @@ * Added alternative emulators support where additional emulators can be defined in es_systems.xml and be selected system-wide or per game via the user interface * Populated the bundled es_systems.xml files with alternative emulator entries for most RetroArch cores * Added a virtual keyboard, partly based on code from batocera-emulationstation +* Added badges that indicate favorite/completed/broken games as well as games suitable for children and those with a selected alternative emulator * Added the ability to make complementary game system customizations without having to replace the entire bundled es_systems.xml file * Added support for an optional \ tag for es_systems.xml that can be used to override the default \ systems sorting +* Added menu scroll indicators showing if there are additional entries available below or above what's currently shown on screen * Improved the gamelist filter screen to not allow filtering of values where there is no actual data to filter, e.g. Favorites for a system with no favorite games * Grayed out all fields in the gamelist filter screen where there is no data to filter, previously some fields were removed entirely and some could still be used * Added the ability to filter on blank/unknown values for Genre, Player, Developer, Publisher and Alternative emulator. @@ -25,6 +27,7 @@ * Made the scrolling speed of ScrollableContainer more consistent across various screen resolutions and display aspect ratios * Made the game name and description stop scrolling when running the media viewer, the screensaver or when running in the background while a game is launched * Added notification popups when plugging in or removing controllers +* Changed to loading the default theme set rbsimple-DE instead of the first available theme if the currently configured theme is missing * Added support for using the left and right trigger buttons in the help prompts * Removed the "Choose" entry from the help prompts in the gamelist view * Changed the "Toggle screensaver" help entry in the system view to simply "Screensaver" @@ -35,6 +38,7 @@ * Added a blinking cursor to TextEditComponent * Changed the filter description "Text filter (game name)" to "Game name" * Added support for multi-select total count and exclusive multi-select to OptionListComponent +* Added support for a maximum name length to OptionListComponent (non-multiselect only) with an abbreviation of the name if it exceeds this value * Added support for key repeat to OptionListComponent, making it possible to cycle through the options by holding the left or right button * Added key repeat for the "Jump to" and "Sort games by" selectors on the game options menu * Added key repeat when editing the "Release date" entry in the metadata editor (DateTimeEditComponent) @@ -74,14 +78,19 @@ * Leading and trailing whitespace characters would get included in scraper search refines and TheGamesDB searches * Game name (text) filters were matching the system names for collection systems if the "Show system names in collections" setting was enabled * Brackets such as () and [] were filtered from game names in collection systems if the "Show system names in collections" setting was enabled +* Fixed multiple issues where ComponentGrid would display incorrect help prompts * Help prompts were missing for the "Rating" and "Release date" fields in the metadata editor * There was some strange behavior in DateTimeEditComponent when changing the date all the way down to 1970-01-01 * When navigating menus, the separator lines and menu components did not align properly and moved up and down slightly +* Under some circumstances and at some screen resolutions, the last menu separator line would not get rendered (still an issue at extreme resolutions like 320x240) * When scrolling in menus, pressing other buttons than "Up" or "Down" did not stop the scrolling which caused all sorts of weird behavior * With the menu scale-up effect enabled and entering a submenu before the parent menu was completely scaled up, the parent would get stuck at a semi-scaled size +* If there was an abbreviated full system name for the "Gamelist on startup" option, that abbreviation would also get displayed when opening the selector window +* Really long theme set names would not get abbreviated in the UI settings menu, leading to a garbled "Theme set" setting row * Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a gamelist on startup had been set * When marking a game to not be counted in the metadata editor and the game was part of a custom collection, no collection disabling notification was displayed * Horizontal sizing of the TextComponent input field was not consistent across different screen resolutions +* The sizing of the metadata editor was strange, which was clearly visible when activating the Ctrl+G debug mode * The "sortname" window header was incorrectly spelled when editing this type of entry in the metadata editor * When the last row of a menu had its text color changed, this color was completely desaturated when navigating to a button below the list diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45d8f5725..fe83c2b6b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,6 @@ This plan is under constant review so expect it to change from time to time. Sti * Support for pre-defined alternative emulators and cores (configured in es_systems.xml) * Badges highlighting things like favorite games, completed games etc. (will require theme support) -* Improved full-screen support, removing the temporary full-screen hacks * Virtual (on-screen) keyboard * Support for the Raspberry Pi 4 (Raspberry Pi OS) * Add GLM library dependency for matrix and vector operations, decommission the built-in functions @@ -39,21 +38,22 @@ This plan is under constant review so expect it to change from time to time. Sti #### v1.3 -* Localization/multi-language support * New theme engine with generalized views (only System and Gamelist) and theme variants support * Add multiple new gamelist components (wheels, wall/grid etc.) * Move existing theme logic to legacy support, only to be used for backwards compatibility +* Improve full-screen support and make game launching more seamless, remove the temporary full-screen hacks * Checksum support for the scraper for exact searches and for determining when to overwrite files -* Improved text and font functions, e.g. faster and cleaner line wrapping and more exact sizing +* Improve text and font functions, e.g. faster and cleaner line wrapping and more exact sizing #### v1.4 +* Localization/multi-language support * Authoring tools to clean up orphaned gamelist entries, media files etc. * Scrollbar component for the gamelist view which can be used by the themes * Web proxy support for the scraper * Add "time played" counter per game, similar to how it works in Steam * Preload all built-in resources and never clear them from the cache -* Improved multi-threading +* Improve multi-threading #### v1.5 diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 25ffbd132..34d760fd6 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -970,6 +970,10 @@ With this option enabled, there will be an overlay displayed when scrolling the This enables a virtual (on-screen) keyboard that can be used at various places throughout the application to input text and numbers using a controller. The Shift and Alt keys can be toggled individually or combined together to access many special characters. The general use of the virtual keyboard should hopefully be self-explanatory. +**Enable menu scroll indicators** + +With this option enabled, "up and down" scroll indicators will be displayed in the upper right corner of menus (including the metadata editor) if there are more entries available than can be shown on the screen at the same time. These indicators will change dynamically as the list is scrolled. If the setting is disabled, a simplified static indicator will be displayed instead. + **Enable toggle favorites button** This setting enables the _Y_ button for quickly toggling a game as favorite. Although this may be convenient at times, it's also quite easy to accidentally remove a favorite tagging of a game when using the application more casually. As such it could sometimes make sense to disable this functionality. It's of course still possible to mark a game as favorite using the metadata editor when this setting is disabled. The option does not affect the use of the _Y_ button to add or remove games when editing custom collections. From cb44762537778a08b16b7eacd9fc3ab7d1d79ce9 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 17:19:37 +0200 Subject: [PATCH 052/128] Made it possible to mark folders with the Kidgame metadata flag. --- es-app/src/MetaData.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 18b3557bb..98a354e59 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -42,21 +42,22 @@ MetaDataDecl gameDecls[] = { }; MetaDataDecl folderDecls[] = { -{"name", MD_STRING, "", false, "name", "enter name", true}, -{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, -{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, -{"releasedate", MD_DATE, "19700101T010000", false, "release date", "enter release date", true}, -{"developer", MD_STRING, "unknown", false, "developer", "enter developer", true}, -{"publisher", MD_STRING, "unknown", false, "publisher", "enter publisher", true}, -{"genre", MD_STRING, "unknown", false, "genre", "enter genre", true}, -{"players", MD_STRING, "unknown", false, "players", "enter number of players", true}, -{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false}, -{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false}, -{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false}, -{"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, -{"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, -{"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, -{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} +{"name", MD_STRING, "", false, "name", "enter name", true}, +{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, +{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, +{"releasedate", MD_DATE, "19700101T010000", false, "release date", "enter release date", true}, +{"developer", MD_STRING, "unknown", false, "developer", "enter developer", true}, +{"publisher", MD_STRING, "unknown", false, "publisher", "enter publisher", true}, +{"genre", MD_STRING, "unknown", false, "genre", "enter genre", true}, +{"players", MD_STRING, "unknown", false, "players", "enter number of players", true}, +{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false}, +{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false}, +{"kidgame", MD_BOOL, "false", false, "kidgame (only affects badges)", "enter kidgame off/on", false}, +{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false}, +{"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, +{"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, +{"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, +{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; // clang-format on From 07425d41fabb98db56759006657df848c02b04cc Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 17:22:29 +0200 Subject: [PATCH 053/128] Fixed an issue with removing invalid alternative emulator entries using the metadata editor. --- es-app/src/guis/GuiMetaDataEd.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index ced319e6c..cf12889a7 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -248,6 +248,11 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, "", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY")); for (auto entry : launchCommands) { + if (mInvalidEmulatorEntry && singleEntry && + entry.second != + ViewController::EXCLAMATION_CHAR + " " + originalValue) + continue; + std::string selectedLabel = ed->getValue(); std::string label; ComponentListRow row; From c2d059f92e5e3e5f7516ef0a0df48a1602c67985 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 17:23:29 +0200 Subject: [PATCH 054/128] (Windows) Fixed two MSVC compiler warnings. --- es-app/src/views/gamelist/DetailedGameListView.cpp | 2 +- es-app/src/views/gamelist/VideoGameListView.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 35808dd29..b343e8b3c 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -106,7 +106,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) addChild(&mBadges); mBadges.setOrigin(0.0f, 0.0f); mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); - mBadges.setSize(mSize.x * 0.15, mSize.y * 0.2f); + mBadges.setSize(mSize.x * 0.15f, mSize.y * 0.2f); mBadges.setDefaultZIndex(50.0f); mName.setPosition(mSize.x, mSize.y); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 6e98be2e1..c0f1c1bc4 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -123,7 +123,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) addChild(&mBadges); mBadges.setOrigin(0.0f, 0.0f); mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); - mBadges.setSize(mSize.x * 0.15, mSize.y * 0.2f); + mBadges.setSize(mSize.x * 0.15f, mSize.y * 0.2f); mBadges.setDefaultZIndex(50.0f); mName.setPosition(mSize.x, mSize.y); From eb611d12dbe8bdb24fb976a79d5c3ea5f6cbc6e5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 18:18:23 +0200 Subject: [PATCH 055/128] Fixed some issues in FlexboxComponent. Also added some sanity checks and size restrictions to BadgeComponent and FlexboxComponent. --- es-core/src/components/BadgesComponent.cpp | 6 +- es-core/src/components/FlexboxComponent.cpp | 87 ++++++++++++++------- es-core/src/components/FlexboxComponent.h | 4 +- 3 files changed, 63 insertions(+), 34 deletions(-) diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index bce5eaeca..00c023b64 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -138,11 +138,11 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("itemMargin")) { const glm::vec2 itemMargin = elem->get("itemMargin"); - if (itemMargin.x < 0.0f || itemMargin.x > 100.0f || itemMargin.y < 0.0f || - itemMargin.y > 100.0f) { + if (itemMargin.x < 0.0f || itemMargin.x > 0.2f || itemMargin.y < 0.0f || + itemMargin.y > 0.2f) { LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" - << itemMargin.x << "x" << itemMargin.y << "\""; + << itemMargin.x << " " << itemMargin.y << "\""; } else { mFlexboxComponent.setItemMargin(itemMargin); diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 4d921e47f..f9b81abd9 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -11,8 +11,8 @@ #define DEFAULT_ITEMS_PER_LINE 4 #define DEFAULT_LINES 2 #define DEFAULT_ITEM_PLACEMENT "center" -#define DEFAULT_MARGIN_X 10.0f -#define DEFAULT_MARGIN_Y 10.0f +#define DEFAULT_MARGIN_X std::roundf(0.01f * Renderer::getScreenWidth()) +#define DEFAULT_MARGIN_Y std::roundf(0.01f * Renderer::getScreenHeight()) #include "components/FlexboxComponent.h" @@ -33,6 +33,32 @@ FlexboxComponent::FlexboxComponent(Window* window, { } +void FlexboxComponent::render(const glm::mat4& parentTrans) +{ + if (!isVisible()) + return; + + if (!mLayoutValid) + computeLayout(); + + glm::mat4 trans{parentTrans * getTransform()}; + Renderer::setMatrix(trans); + + if (Settings::getInstance()->getBool("DebugImage")) + Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); + + for (auto& image : mImages) { + if (mOpacity == 255) { + image.second.render(trans); + } + else { + image.second.setOpacity(mOpacity); + image.second.render(trans); + image.second.setOpacity(255); + } + } +} + void FlexboxComponent::computeLayout() { // Start placing items in the top-left. @@ -49,19 +75,35 @@ void FlexboxComponent::computeLayout() directionRow = {1, 0}; } + // If we're not clamping itemMargin to a reasonable value, all kinds of weird rendering + // issues could occur. + mItemMargin.x = glm::clamp(mItemMargin.x, 0.0f, mSize.x / 2.0f); + mItemMargin.y = glm::clamp(mItemMargin.y, 0.0f, mSize.y / 2.0f); + + // Also keep the size within reason. + mSize.x = glm::clamp(mSize.x, static_cast(Renderer::getScreenWidth()) * 0.03f, + static_cast(Renderer::getScreenWidth())); + mSize.y = glm::clamp(mSize.y, static_cast(Renderer::getScreenHeight()) * 0.03f, + static_cast(Renderer::getScreenHeight())); + // Compute maximum image dimensions. glm::vec2 grid; if (mDirection == "row") grid = {mItemsPerLine, mLines}; else grid = {mLines, mItemsPerLine}; + glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid}; + maxItemSize.x = floorf(maxItemSize.x); + maxItemSize.y = floorf(maxItemSize.y); if (grid.x * grid.y < static_cast(mImages.size())) { LOG(LogWarning) << "FlexboxComponent: Invalid theme configuration, the number of badges " "exceeds the product of times "; } + glm::vec2 sizeChange{0.0f, 0.0f}; + // Set final image dimensions. for (auto& image : mImages) { if (!image.second.isVisible()) @@ -78,9 +120,22 @@ void FlexboxComponent::computeLayout() newSize = sizeMaxX; else newSize = sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; - image.second.setResize(newSize.x, newSize.y); + + if (image.second.getSize() != newSize) + image.second.setResize(newSize.x, newSize.y); + + // In case maxItemSize needs to be updated. + if (newSize.x != sizeChange.x) + sizeChange.x = newSize.x; + if (newSize.y != sizeChange.y) + sizeChange.y = newSize.y; } + if (maxItemSize.x != sizeChange.x) + maxItemSize.x = sizeChange.x; + if (maxItemSize.y != sizeChange.y) + maxItemSize.y = sizeChange.y; + // Pre-compute layout parameters. float anchorXStart{anchorX}; float anchorYStart{anchorY}; @@ -140,29 +195,3 @@ void FlexboxComponent::computeLayout() mLayoutValid = true; } - -void FlexboxComponent::render(const glm::mat4& parentTrans) -{ - if (!isVisible()) - return; - - if (!mLayoutValid) - computeLayout(); - - glm::mat4 trans{parentTrans * getTransform()}; - Renderer::setMatrix(trans); - - if (Settings::getInstance()->getBool("DebugImage")) - Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); - - for (auto& image : mImages) { - if (mOpacity == 255) { - image.second.render(trans); - } - else { - image.second.setOpacity(mOpacity); - image.second.render(trans); - image.second.setOpacity(255); - } - } -} diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index bbe385aba..41c24433e 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -15,10 +15,10 @@ class FlexboxComponent : public GuiComponent { public: - explicit FlexboxComponent(Window* window, - std::vector>& images); + FlexboxComponent(Window* window, std::vector>& images); // Getters/setters for the layout. + std::string getDirection() const { return mDirection; } void setDirection(const std::string& direction) { assert(direction == "row" || direction == "column"); From d0ccc8a13d42c7ab4e241a040497271bbc72425b Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 13 Oct 2021 18:47:34 +0200 Subject: [PATCH 056/128] Documentation update. --- CHANGELOG.md | 1 + THEMES-DEV.md | 50 +++++++++++++++++++++--------------------------- USERGUIDE-DEV.md | 8 +++++--- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d7e306b4..a7a83df73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ * Added support for key repeat to OptionListComponent, making it possible to cycle through the options by holding the left or right button * Added key repeat for the "Jump to" and "Sort games by" selectors on the game options menu * Added key repeat when editing the "Release date" entry in the metadata editor (DateTimeEditComponent) +* Added support for setting the Kidgame metadata flag for folders (which will only affect the badges) * Achieved a massive speed improvement for OptionListComponent by not resizing each added MenuComponent row (most notable in the filter GUI) * Made multiple optimizations to the GUI components by removing lots of unnecessary function calls for sizing, placement, opacity changes etc. * Simplified the logic for info popups and prepared the code for the future "multiple popups" feature diff --git a/THEMES-DEV.md b/THEMES-DEV.md index b2a659cf0..e13aea2c4 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -633,7 +633,7 @@ Can be created as an extra. * `rotation` - type: FLOAT. - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the image will be rotated. Defaults to `0.5 0.5`. + - Point around which the image will be rotated. Default is `0.5 0.5`. * `path` - type: PATH. - Path to the image file. Most common extensions are supported (including .jpg, .png, and unanimated .gif). * `default` - type: PATH. @@ -707,7 +707,7 @@ Can be created as an extra. * `rotation` - type: FLOAT. - angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the text will be rotated. Defaults to `0.5 0.5`. + - Point around which the text will be rotated. Default is `0.5 0.5`. * `delay` - type: FLOAT. Default is false. - Delay in seconds before video will start playing. * `default` - type: PATH. @@ -736,7 +736,7 @@ Can be created as an extra. * `rotation` - type: FLOAT. - angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the text will be rotated. Defaults to `0.5 0.5`. + - Point around which the text will be rotated. Default is `0.5 0.5`. * `text` - type: STRING. * `color` - type: COLOR. * `backgroundColor` - type: COLOR; @@ -808,7 +808,7 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren * `rotation` - type: FLOAT. - angle in degrees that the rating should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the rating will be rotated. Defaults to `0.5 0.5`. + - Point around which the rating will be rotated. Default is `0.5 0.5`. * `filledPath` - type: PATH. - Path to the "filled star" image. Image must be square (width equals height). * `unfilledPath` - type: PATH. @@ -832,7 +832,7 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren * `rotation` - type: FLOAT. - angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the text will be rotated. Defaults to `0.5 0.5`. + - Point around which the text will be rotated. Default is `0.5 0.5`. * `color` - type: COLOR. * `backgroundColor` - type: COLOR; * `fontPath` - type: PATH. @@ -913,41 +913,35 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren * `pos` - type: NORMALIZED_PAIR. * `size` - type: NORMALIZED_PAIR. - Possible combinations: - - `w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. + - `w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. Minimum value per axis is `0.03`, maximum value is `1.0`. Default is `0.15 0.20`. * `origin` - type: NORMALIZED_PAIR. - - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the component exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied. + - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the component exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied. Default is `0 0`. * `rotation` - type: FLOAT. - - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. + - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Default is `0`. * `rotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the image will be rotated. Defaults to `0.5 0.5`. + - Point around which the image will be rotated. Default is `0.5 0.5`. * `itemsPerRow` - type: FLOAT. - - Number of badges that fit on a row. When more badges are available a new row will be started. + - Number of badges that fit on a row. When more badges are available a new row will be started. Default is `4`. * `rows` - type: FLOAT. - - The number of rows available. + - The number of rows available. Default is `2`. * `itemPlacement` - type: STRING. - - Valid values are "top", "center", "bottom", or "stretch". Controls vertical alignment of each badge if images of different heights are used. "Stretch" will stretch the badge to the full height. + - Valid values are "top", "center", "bottom", or "stretch". Controls vertical alignment of each badge if images of different heights are used. "Stretch" will stretch the badge to the full height. Default is `center`. * `itemMargin` - type: NORMALIZED_PAIR. - The margins between badges. Possible combinations: - - `x y` - horizontal and vertical margins. + - `x y` - horizontal and vertical margins. Minimum value per axis is `0`, maximum value is `0.2`. Default is `0.01`. * `slots` - type: STRING. - - The badge types that should be displayed. Should be specified as a list of strings separated by spaces. The order will be followed when placing badges on the screen. - - Available badges are: - - "favorite": Will be shown when the game is marked as favorite. - - "completed": Will be shown when the game is marked as completed. - - "kidgame": Will be shown when the game is marked as a kids game. - - "broken": Will be shown when the game is marked as broken. - - "altemulator": Will be shown when an alternative emulator is setup for the game. + - The badge types that should be displayed. Should be specified as a list of strings separated by spaces. The order will be followed when placing badges on the screen. Available badges are: + - `favorite`: Will be shown when the game is marked as favorite. + - `completed`: Will be shown when the game is marked as completed. + - `kidgame`: Will be shown when the game is marked as a kids game. + - `broken`: Will be shown when the game is marked as broken. + - `altemulator`: Will be shown when an alternative emulator is setup for the game. * `customBadgeIcon` - type: PATH. - - A badge icon override. Specify the badge type in the attribute `badge`. The available badges are: - `favorite`, - `completed`, - `kidgame`, - `broken`, - `altemulator` + - A badge icon override. Specify the badge type in the attribute `badge`. The available badges are the ones listed above. * `visible` - type: BOOLEAN. - If true, component will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. * `zIndex` - type: FLOAT. - - z-index value for component. Components will be rendered in order of z-index value from low to high. + - z-index value for component. Components will be rendered in order of z-index value from low to high. Default is `50`. #### carousel @@ -971,7 +965,7 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren - Default is 7.5 - This property only applies when `type` is "horizontal_wheel" or "vertical_wheel". * `logoRotationOrigin` - type: NORMALIZED_PAIR. - - Point around which the logos will be rotated. Defaults to `-5 0.5`. + - Point around which the logos will be rotated. Default is `-5 0.5`. - This property only applies when `type` is "horizontal_wheel" or "vertical_wheel". * `logoAlignment` - type: STRING. - Sets the alignment of the logos relative to the carousel. diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 34d760fd6..c0117dc04 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -213,6 +213,8 @@ In addition to the styles just described, there is a **Grid** view style as well If the theme supports it, there's a gamelist information field displayed in the gamelist view, showing the number of games for the system (total and favorites) as well as a folder icon if a folder has been entered. When applying any filters to the gamelist, the game counter is replaced with the amount of games filtered, as in 'filtered / total games', e.g. '19 / 77'. If there are game entries in the filter result that are marked not to be counted as games, the number of such files will be indicated as 'filtered + filtered non-games / total games', for example '23 + 4 / 77' indicating 23 normal games, 4 non-games out of a total of 77. Due to this approach it's theoretically possible that the combined filtered game amount exceeds the number of counted games in the collection, for instance '69 + 11 / 77'. This is not considered a bug and is so by design. This gamelist information field functionality is specific to EmulationStation Desktop Edition so older themes will not support this. +Another feature which requires theme support is **Badges**, which is a set of icons displaying the status for various metadata fields. The currently supported badge types are _favorite, completed, kidgame, broken_ and _alternative emulator_. If any of the first four metadata fields have been set for a game, their corresponding badges will be displayed, and if an alternative emulator has been selected for the specific game, that badge will be shown. Setting an alternative emulator system-wide will not display this badge as it's only intended to indicate game-specific overrides. + ![alt text](images/es-de_gamelist_view.png "ES-DE Gamelist View") _The **Gamelist view** is where you browse the games for a specific system._ @@ -1444,9 +1446,9 @@ A flag to indicate whether this is a favorite game. This flag can also be set di A flag to indicate whether you have completed the game. -**Kidgame** _(files only)_ +**Kidgame** -A flag to mark whether the game is suitable for children. This will be applied as a filter when starting ES-DE in _Kid_ mode. +A flag to mark whether the game is suitable for children. This will be applied as a filter when starting ES-DE in _Kid_ mode. Although it's possible to also set this flag for folders, this will **not** affect the actual files inside those folders. It will instead only be used to display the Kidgame badge for the folders themselves. **Hidden** @@ -1466,7 +1468,7 @@ Whether to exclude the file from the multi-scraper. This is quite useful in orde **Hide metadata fields** -This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. +This option will hide most metadata fields as well as any badges. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. **Times played** _(files only)_ From a1ed59553f12f691b2ce48aee551d9e9a7e883b9 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 14 Oct 2021 21:29:23 +0200 Subject: [PATCH 057/128] Made it possible to set a 'a/select' help prompt for TextComponent. --- es-core/src/components/TextComponent.cpp | 58 ++++++++++++++---------- es-core/src/components/TextComponent.h | 5 ++ 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index ad4dda89f..fa15ce628 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -13,18 +13,19 @@ #include "utils/StringUtil.h" TextComponent::TextComponent(Window* window) - : GuiComponent(window) - , mFont(Font::get(FONT_SIZE_MEDIUM)) - , mColor(0x000000FF) - , mBgColor(0) - , mMargin(0.0f) - , mRenderBackground(false) - , mUppercase(false) - , mAutoCalcExtent(true, true) - , mHorizontalAlignment(ALIGN_LEFT) - , mVerticalAlignment(ALIGN_CENTER) - , mLineSpacing(1.5f) - , mNoTopMargin(false) + : GuiComponent{window} + , mFont{Font::get(FONT_SIZE_MEDIUM)} + , mColor{0x000000FF} + , mBgColor{0} + , mMargin{0.0f} + , mRenderBackground{false} + , mUppercase{false} + , mAutoCalcExtent{1, 1} + , mHorizontalAlignment{ALIGN_LEFT} + , mVerticalAlignment{ALIGN_CENTER} + , mLineSpacing{1.5f} + , mNoTopMargin{false} + , mSelectable{false} { } @@ -37,18 +38,19 @@ TextComponent::TextComponent(Window* window, glm::vec2 size, unsigned int bgcolor, float margin) - : GuiComponent(window) - , mFont(nullptr) - , mColor(0x000000FF) - , mBgColor(0) - , mMargin(margin) - , mRenderBackground(false) - , mUppercase(false) - , mAutoCalcExtent(true, true) - , mHorizontalAlignment(align) - , mVerticalAlignment(ALIGN_CENTER) - , mLineSpacing(1.5f) - , mNoTopMargin(false) + : GuiComponent{window} + , mFont{nullptr} + , mColor{0x000000FF} + , mBgColor{0} + , mMargin{margin} + , mRenderBackground{false} + , mUppercase{false} + , mAutoCalcExtent{1, 1} + , mHorizontalAlignment{align} + , mVerticalAlignment{ALIGN_CENTER} + , mLineSpacing{1.5f} + , mNoTopMargin{false} + , mSelectable{false} { setFont(font); setColor(color); @@ -282,6 +284,14 @@ void TextComponent::setNoTopMargin(bool margin) onTextChanged(); } +std::vector TextComponent::getHelpPrompts() +{ + std::vector prompts; + if (mSelectable) + prompts.push_back(HelpPrompt("a", "select")); + return prompts; +} + void TextComponent::applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index 57a5561c5..cd07f5592 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -60,11 +60,15 @@ public: unsigned char getOpacity() const override { return mColor & 0x000000FF; } void setOpacity(unsigned char opacity) override; + void setSelectable(bool status) { mSelectable = status; } + virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; + virtual std::vector getHelpPrompts() override; + unsigned int getColor() const override { return mColor; } std::shared_ptr getFont() const override { return mFont; } Alignment getHorizontalAlignment() { return mHorizontalAlignment; } @@ -95,6 +99,7 @@ private: Alignment mVerticalAlignment; float mLineSpacing; bool mNoTopMargin; + bool mSelectable; }; #endif // ES_CORE_COMPONENTS_TEXT_COMPONENT_H From fe5e3ad5d422135beacb65263aff9b78818cc4d5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 14 Oct 2021 21:47:32 +0200 Subject: [PATCH 058/128] The alternative emulators GUI now looks good at all resolutions. --- es-app/src/guis/GuiAlternativeEmulators.cpp | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/es-app/src/guis/GuiAlternativeEmulators.cpp b/es-app/src/guis/GuiAlternativeEmulators.cpp index 50420a17b..e19de6ef9 100644 --- a/es-app/src/guis/GuiAlternativeEmulators.cpp +++ b/es-app/src/guis/GuiAlternativeEmulators.cpp @@ -39,13 +39,6 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window) ComponentListRow row; - // This transparent bracket is only added to generate a left margin. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - bracket->setSize(bracket->getSize() / 3.0f); - row.addElement(bracket, false); - std::string name = (*it)->getName(); std::shared_ptr systemText = std::make_shared(mWindow, name, Font::get(FONT_SIZE_MEDIUM), 0x777777FF); @@ -94,7 +87,9 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window) labelText->setColor(TEXTCOLOR_SCRAPERMARKED); mCommandRows[name] = labelText; - labelText->setSize(labelSizeX, labelText->getSize().y); + labelText->setSize(mMenu.getSize().x - systemSizeX - + 20.0f * Renderer::getScreenHeightModifier(), + systemText->getSize().y); row.addElement(labelText, false); row.makeAcceptInputHandler([this, it, labelText] { @@ -157,6 +152,7 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system) std::shared_ptr labelText = std::make_shared( mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_LEFT); + labelText->setSelectable(true); if (system->getSystemEnvData()->mLaunchCommands.front().second == label) labelText->setValue(labelText->getValue().append(" [DEFAULT]")); @@ -193,13 +189,6 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system) delete s; }); - // This transparent bracket is only added to generate the correct help prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - bracket->setSize(bracket->getSize() / 3.0f); - row.addElement(bracket, false); - // Select the row that corresponds to the selected label. if (selectedLabel == label) s->addRow(row, true); From 10e284c92972bbc8148e098eb8172798ffae4ebc Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 14 Oct 2021 21:59:09 +0200 Subject: [PATCH 059/128] Text in ScrollableContainer now mostly stays within the initial area. --- es-core/src/components/ScrollableContainer.cpp | 11 +++++++++++ es-core/src/components/ScrollableContainer.h | 1 + 2 files changed, 12 insertions(+) diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index 218b7cd3a..51f33f917 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -18,6 +18,7 @@ ScrollableContainer::ScrollableContainer(Window* window) : GuiComponent{window} , mScrollPos{0.0f, 0.0f} , mScrollDir{0.0f, 0.0f} + , mClipSpacing{0.0f} , mAutoScrollDelay{0} , mAutoScrollSpeed{0} , mAutoScrollAccumulator{0} @@ -81,6 +82,11 @@ void ScrollableContainer::update(int deltaTime) float lineSpacing{mChildren.front()->getLineSpacing()}; float combinedHeight{mChildren.front()->getFont()->getHeight(lineSpacing)}; + // Calculate the line spacing which will be used to clip the container. + if (mClipSpacing == 0.0f) + mClipSpacing = + std::round((combinedHeight - mChildren.front()->getFont()->getLetterHeight()) / 2.0f); + // Resize container to font height boundary to avoid rendering a fraction of the last line. if (!mUpdatedSize && contentSize.y > mSize.y) { float numLines{mSize.y / combinedHeight}; @@ -173,6 +179,11 @@ void ScrollableContainer::render(const glm::mat4& parentTrans) 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. + clipPos.y += mClipSpacing; + clipDim.y -= mClipSpacing * 0.9f; + Renderer::pushClipRect(clipPos, clipDim); trans = glm::translate(trans, -glm::vec3{mScrollPos.x, mScrollPos.y, 0.0f}); diff --git a/es-core/src/components/ScrollableContainer.h b/es-core/src/components/ScrollableContainer.h index 27b63f459..fb80b7eea 100644 --- a/es-core/src/components/ScrollableContainer.h +++ b/es-core/src/components/ScrollableContainer.h @@ -44,6 +44,7 @@ private: float mAutoScrollDelayConstant; float mAutoScrollSpeedConstant; float mResolutionModifier; + float mClipSpacing; int mAutoScrollDelay; int mAutoScrollSpeed; From eed27d1ee30f5282caeb4cf72c31c3bfef00fb27 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 14 Oct 2021 22:13:13 +0200 Subject: [PATCH 060/128] Changed the font size for the custom collection deletion screen. Also fixed an issue with incorrect row heights at lower resolutions and removed a help prompt hack. --- .../src/guis/GuiCollectionSystemsOptions.cpp | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp index 2d1e31059..fd18b1cb5 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -177,12 +177,8 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st row.makeAcceptInputHandler(createCollectionCall); auto themeFolder = std::make_shared( mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF); + themeFolder->setSelectable(true); row.addElement(themeFolder, true); - // This transparent bracket is only added to generate the correct help prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - row.addElement(bracket, false); ss->addRow(row); } mWindow->pushGui(ss); @@ -287,15 +283,17 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st }; row.makeAcceptInputHandler(deleteCollectionCall); auto customCollection = std::make_shared( - mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF); + mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + customCollection->setSelectable(true); row.addElement(customCollection, true); - // This transparent bracket is only added generate the correct help prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - row.addElement(bracket, false); ss->addRow(row); } + // Make the menu slightly wider to fit the scroll indicators. + glm::vec2 menuSize{ss->getMenuSize()}; + glm::vec3 menuPos{ss->getMenuPosition()}; + ss->setMenuSize(glm::vec2{menuSize.x * 1.08f, menuSize.y}); + menuPos.x = static_cast((Renderer::getScreenWidth()) - ss->getMenuSize().x) / 2.0f; + ss->setMenuPosition(menuPos); mWindow->pushGui(ss); }); addRow(row); From b4045f05aee9a572f2373d3c6e750a6c0dbe1d8c Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 14 Oct 2021 22:21:21 +0200 Subject: [PATCH 061/128] Removed some help prompt hacks in GuiMenu. --- es-app/src/guis/GuiMenu.cpp | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 74f707d68..f339f06ee 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -1207,11 +1207,6 @@ void GuiMenu::openQuitMenu() Window* window = mWindow; HelpStyle style = getHelpStyle(); - // This transparent bracket is only neeeded to generate the correct help prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - ComponentListRow row; row.makeAcceptInputHandler([window, this] { @@ -1224,10 +1219,10 @@ void GuiMenu::openQuitMenu() }, "NO", nullptr)); }); - row.addElement(std::make_shared(window, "QUIT EMULATIONSTATION", - Font::get(FONT_SIZE_MEDIUM), 0x777777FF), - true); - row.addElement(bracket, false); + auto quitText = std::make_shared(window, "QUIT EMULATIONSTATION", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + quitText->setSelectable(true); + row.addElement(quitText, true); s->addRow(row); row.elements.clear(); @@ -1243,10 +1238,10 @@ void GuiMenu::openQuitMenu() }, "NO", nullptr)); }); - row.addElement(std::make_shared(window, "REBOOT SYSTEM", - Font::get(FONT_SIZE_MEDIUM), 0x777777FF), - true); - row.addElement(bracket, false); + auto rebootText = std::make_shared(window, "REBOOT SYSTEM", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + rebootText->setSelectable(true); + row.addElement(rebootText, true); s->addRow(row); row.elements.clear(); @@ -1262,10 +1257,10 @@ void GuiMenu::openQuitMenu() }, "NO", nullptr)); }); - row.addElement(std::make_shared(window, "POWER OFF SYSTEM", - Font::get(FONT_SIZE_MEDIUM), 0x777777FF), - true); - row.addElement(bracket, false); + auto powerOffText = std::make_shared( + window, "POWER OFF SYSTEM", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + powerOffText->setSelectable(true); + row.addElement(powerOffText, true); s->addRow(row); mWindow->pushGui(s); From 6ccee6e4c4ee87379c0217304756f6540d497f10 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 14 Oct 2021 22:31:50 +0200 Subject: [PATCH 062/128] Removed an unnecessary column from GuiMetaDataEd. Also moved the scroll indicators slightly and removed a help prompt hack. --- es-app/src/guis/GuiMetaDataEd.cpp | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index cf12889a7..594b55fe6 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -43,7 +43,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, std::function deleteGameFunc) : GuiComponent{window} , mBackground{window, ":/graphics/frame.svg"} - , mGrid{window, glm::ivec2{3, 6}} + , mGrid{window, glm::ivec2{2, 6}} , mScraperParams{scraperParams} , mMetaDataDecl{mdd} , mMetaData{md} @@ -58,7 +58,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, mTitle = std::make_shared(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); - mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{3, 2}); + mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2}); // Extract possible subfolders from the path. std::string folderPath = @@ -83,10 +83,10 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER, glm::vec3{}, glm::vec2{}, 0x00000000, 0.05f); - mGrid.setEntry(mSubtitle, glm::ivec2{0, 2}, false, true, glm::ivec2{3, 1}); + mGrid.setEntry(mSubtitle, glm::ivec2{0, 2}, false, true, glm::ivec2{2, 1}); mList = std::make_shared(mWindow); - mGrid.setEntry(mList, glm::ivec2{0, 4}, true, true, glm::ivec2{3, 1}); + mGrid.setEntry(mList, glm::ivec2{0, 4}, true, true, glm::ivec2{2, 1}); // Set up scroll indicators. mScrollUp = std::make_shared(mWindow); @@ -99,8 +99,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, mScrollDown->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f); mScrollDown->setOrigin(0.0f, 0.35f); - mGrid.setEntry(mScrollUp, glm::ivec2{2, 0}, false, false, glm::ivec2{1, 1}); - mGrid.setEntry(mScrollDown, glm::ivec2{2, 1}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1}); // Populate list. for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) { @@ -265,6 +265,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, std::shared_ptr labelText = std::make_shared( mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + labelText->setSelectable(true); if (scraperParams.system->getAlternativeEmulator() == "" && scraperParams.system->getSystemEnvData() @@ -289,14 +290,6 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, delete s; }); - // This transparent bracket is only added to generate the correct help - // prompts. - auto bracket = std::make_shared(mWindow); - bracket->setImage(":/graphics/arrow.svg"); - bracket->setOpacity(0); - bracket->setSize(bracket->getSize() / 3.0f); - row.addElement(bracket, false); - // Select the row that corresponds to the selected label. if (selectedLabel == label) s->addRow(row, true); @@ -481,7 +474,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, } mButtons = makeButtonGrid(mWindow, buttons); - mGrid.setEntry(mButtons, glm::ivec2{0, 5}, true, false, glm::ivec2{3, 1}); + mGrid.setEntry(mButtons, glm::ivec2{0, 5}, true, false, glm::ivec2{2, 1}); // Resize + center. float width = @@ -505,8 +498,7 @@ void GuiMetaDataEd::onSizeChanged() mGrid.setRowHeightPerc(3, (titleSubtitleSpacing * 1.2f) / mSize.y); mGrid.setRowHeightPerc(4, ((mList->getRowHeight(0) * 10.0f) + 2.0f) / mSize.y); - mGrid.setColWidthPerc(0, 0.07f); - mGrid.setColWidthPerc(2, 0.07f); + mGrid.setColWidthPerc(1, 0.055f); mGrid.setSize(mSize); mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); From 7311a49f82258ad2175a1c114c01e02e3dfd8b50 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 14 Oct 2021 22:38:30 +0200 Subject: [PATCH 063/128] Removed an unnecessary column from MenuComponent and adjusted the scroll indicators. --- es-core/src/components/MenuComponent.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/es-core/src/components/MenuComponent.cpp b/es-core/src/components/MenuComponent.cpp index 4f8a2dbf9..e7c3c0964 100644 --- a/es-core/src/components/MenuComponent.cpp +++ b/es-core/src/components/MenuComponent.cpp @@ -21,7 +21,7 @@ MenuComponent::MenuComponent(Window* window, const std::shared_ptr& titleFont) : GuiComponent(window) , mBackground(window) - , mGrid(window, glm::ivec2{3, 4}) + , mGrid(window, glm::ivec2{2, 4}) , mNeedsSaving(false) { addChild(&mBackground); @@ -34,11 +34,11 @@ MenuComponent::MenuComponent(Window* window, mTitle->setHorizontalAlignment(ALIGN_CENTER); mTitle->setColor(0x555555FF); setTitle(title, titleFont); - mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{3, 2}); + mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2}); // Set up list which will never change (externally, anyway). mList = std::make_shared(mWindow); - mGrid.setEntry(mList, glm::ivec2{0, 2}, true, true, glm::ivec2{3, 1}); + mGrid.setEntry(mList, glm::ivec2{0, 2}, true, true, glm::ivec2{2, 1}); // Set up scroll indicators. mScrollUp = std::make_shared(mWindow); @@ -51,8 +51,8 @@ MenuComponent::MenuComponent(Window* window, mScrollDown->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f); mScrollDown->setOrigin(0.0f, 0.35f); - mGrid.setEntry(mScrollUp, glm::ivec2{2, 0}, false, false, glm::ivec2{1, 1}); - mGrid.setEntry(mScrollDown, glm::ivec2{2, 1}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1}); updateGrid(); updateSize(); @@ -127,8 +127,7 @@ void MenuComponent::onSizeChanged() mGrid.setRowHeightPerc(1, TITLE_HEIGHT / mSize.y / 2.0f); mGrid.setRowHeightPerc(3, getButtonGridHeight() / mSize.y); - mGrid.setColWidthPerc(0, 0.07f); - mGrid.setColWidthPerc(2, 0.07f); + mGrid.setColWidthPerc(1, 0.055f); mGrid.setSize(mSize); } @@ -152,7 +151,7 @@ void MenuComponent::updateGrid() if (mButtons.size()) { mButtonGrid = makeButtonGrid(mWindow, mButtons); - mGrid.setEntry(mButtonGrid, glm::ivec2{0, 3}, true, false, glm::ivec2{3, 1}); + mGrid.setEntry(mButtonGrid, glm::ivec2{0, 3}, true, false, glm::ivec2{2, 1}); } } From 52b516b9ca5ff946f17868a0508555f90b9c6d4f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 14 Oct 2021 22:48:54 +0200 Subject: [PATCH 064/128] Updated the default badges graphics. --- resources/graphics/badge_altemulator.svg | 335 +++++++++++------------ resources/graphics/badge_broken.svg | 146 ++-------- resources/graphics/badge_completed.svg | 144 ++-------- resources/graphics/badge_favorite.svg | 140 ++-------- resources/graphics/badge_kidgame.svg | 146 ++-------- themes/rbsimple-DE/theme.xml | 4 +- 6 files changed, 232 insertions(+), 683 deletions(-) diff --git a/resources/graphics/badge_altemulator.svg b/resources/graphics/badge_altemulator.svg index 0a07c9872..055f15766 100644 --- a/resources/graphics/badge_altemulator.svg +++ b/resources/graphics/badge_altemulator.svg @@ -8,8 +8,8 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="80" - height="112" - viewBox="0 0 21.166666 29.633334" + height="92" + viewBox="0 0 21.166666 24.341667" version="1.1" id="svg4842" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" @@ -23,9 +23,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="12.442154" - inkscape:cx="-18.41824" - inkscape:cy="59.681612" + inkscape:zoom="19.3509" + inkscape:cx="5.374237" + inkscape:cy="26.957777" inkscape:document-units="mm" inkscape:current-layer="layer2" showgrid="false" @@ -59,155 +59,12 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-1.9829021e-4,-266.11715)"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + transform="translate(-1.9829021e-4,-271.40882)"> + style="fill:#ffffff;stroke-width:0.25911999"> + + id="g16" + transform="matrix(0.10079384,0,0,0.08235571,-2.2547839e-4,271.45845)" + style="fill:#d7d7d7;fill-opacity:1"> + + + + + + + + + @@ -380,7 +281,7 @@ + style="opacity:1;vector-effect:none;fill:#ffd700;fill-opacity:1;stroke:#282828;stroke-width:0.21396799;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers" /> - - + aria-label="ALTERNATIVE" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.60125422px;line-height:1.25;font-family:FontAwesome;-inkscape-font-specification:FontAwesome;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="text4942" + transform="matrix(1.1491297,0,0,1.5857309,-1.3267257,-15.489625)"> + d="m 3.9524717,26.040316 q 0,-0.01016 0.012066,-0.07938 0.033659,-0.208938 0.076844,-0.432484 0.043185,-0.22418 0.1117727,-0.555688 0.068588,-0.332142 0.089545,-0.435659 0.1270144,-0.0057 0.3492895,-0.0057 0.2070334,0 0.3619909,0.0057 0.010161,0 0.019052,0.0076 0.00889,0.007 0.010796,0.01778 0.027943,0.141621 0.094626,0.462967 0.067318,0.321347 0.1143129,0.56839 0.04763,0.246408 0.081289,0.467413 -0.1200285,0.0057 -0.221005,0.0057 -0.1619433,0 -0.2489481,-0.0057 -0.010796,-6.35e-4 -0.019687,-0.0076 -0.00826,-0.0076 -0.010161,-0.01778 -0.028578,-0.146067 -0.04636,-0.254029 H 4.4922827 q -0.020322,0.135905 -0.0489,0.279432 -0.1219338,0.0057 -0.2102088,0.0057 -0.1054219,0 -0.2597444,-0.0057 -0.00953,0 -0.015242,-0.0057 -0.00572,-0.0064 -0.00572,-0.01524 z m 0.5823608,-0.588711 h 0.1428912 q -0.015242,-0.104152 -0.04128,-0.296579 -0.026038,-0.193062 -0.037469,-0.273081 -0.012701,0.103517 -0.033659,0.297214 -0.020957,0.193062 -0.030483,0.272446 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4944" + inkscape:connector-curvature="0" /> + d="m 5.3655065,25.299187 q 0,-0.35564 0.010161,-0.762086 0.1206636,-0.0057 0.2222751,-0.0057 0.1047869,0 0.2603795,0.0057 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.010161,0.477574 0.010161,0.736683 0,0.168929 -0.00191,0.329603 h 0.010796 q 0.2318012,0 0.3873938,0.0057 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00254,0.154958 0.00254,0.219735 0,0.09971 -0.00254,0.186711 -0.1206637,0.0057 -0.3746924,0.0057 -0.062872,0 -0.2680003,-0.0044 -0.2044932,-0.0038 -0.2718108,-0.0063 -0.010161,0 -0.017782,-0.0076 -0.00762,-0.0076 -0.00762,-0.01778 -0.010161,-0.477574 -0.010161,-0.736684 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4946" + inkscape:connector-curvature="0" /> + d="m 6.2012611,24.730163 q 0,-0.102247 0.00254,-0.193062 0.1212988,-0.0057 0.5880766,-0.0057 0.3689767,0 0.5245693,0.0057 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00254,0.143526 0.00254,0.215924 0,0.103517 -0.00254,0.190522 -0.04128,0.0019 -0.3073748,0.0044 0.00254,0.137175 0.00254,0.325791 0,0.355641 -0.010161,0.762087 -0.1206636,0.0057 -0.2222751,0.0057 -0.1047869,0 -0.2603795,-0.0057 -0.010161,0 -0.017782,-0.0076 -0.00762,-0.0076 -0.00762,-0.01778 -0.010161,-0.477574 -0.010161,-0.736684 0,-0.117488 0.00254,-0.328332 -0.1460665,-0.0032 -0.2819719,-0.007 -0.010161,0 -0.017782,-0.0076 -0.00762,-0.0076 -0.00762,-0.01778 -0.00254,-0.146067 -0.00254,-0.208304 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4948" + inkscape:connector-curvature="0" /> + d="m 7.4485421,25.299187 q 0,-0.35564 0.010161,-0.762086 0.1206637,-0.0057 0.5271097,-0.0057 0.2876875,0 0.4432801,0.0057 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00254,0.154958 0.00254,0.219735 0,0.09971 -0.00254,0.186711 -0.1073272,0.0051 -0.4039057,0.0051 -0.053346,0 -0.096531,-6.35e-4 -0.00127,0.08764 -0.00191,0.133365 0.2273557,0.0013 0.3041994,0.0044 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00191,0.104152 0.00191,0.191157 0,0.07811 -0.00191,0.169564 H 7.9521541 q 6.35e-4,0.04445 0.00191,0.132095 h 0.030483 q 0.3111852,0 0.4496309,0.0051 0.010161,0 0.017782,0.0076 0.00762,0.0076 0.00762,0.01778 0.00254,0.154958 0.00254,0.219735 0,0.09971 -0.00254,0.186711 -0.1206637,0.0057 -0.4356593,0.0057 -0.062872,0 -0.2680003,-0.0044 -0.2044932,-0.0038 -0.2718108,-0.0063 -0.010161,0 -0.017782,-0.0076 -0.00762,-0.0076 -0.00762,-0.01778 -0.010161,-0.477574 -0.010161,-0.736684 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4950" + inkscape:connector-curvature="0" /> + d="m 8.6069129,25.266164 q 0,-0.127015 0.00254,-0.35183 0.00254,-0.224816 0.00254,-0.35183 0.1270144,-0.01969 0.2699055,-0.03112 0.1435263,-0.01207 0.2508534,-0.01207 0.092721,0 0.1555926,0.0032 0.062872,0.0025 0.1352703,0.0127 0.073033,0.0095 0.1200286,0.02794 0.046995,0.01842 0.093991,0.05081 0.046995,0.03239 0.073033,0.07875 0.026673,0.04636 0.043185,0.113678 0.016512,0.06732 0.016512,0.154322 0,0.137811 -0.071128,0.242598 -0.071128,0.104786 -0.1981424,0.164483 0.08637,0.02985 0.1403509,0.121934 0.1263793,0.248313 0.2019528,0.571565 -0.1168532,0.01397 -0.2660951,0.01397 -0.1079622,0 -0.2038581,-0.0064 -0.012066,-6.35e-4 -0.023498,-0.0076 -0.011431,-0.007 -0.015242,-0.01778 -0.123839,-0.375963 -0.1924268,-0.607129 h -0.033659 q 0.00127,0.07113 0.00572,0.291498 0.00508,0.22037 0.00572,0.333413 -0.1041518,0.0044 -0.2229102,0.0044 -0.1073272,0 -0.2597444,-0.0044 -0.010796,0 -0.018417,-0.007 -0.00699,-0.0076 -0.00699,-0.01842 0,-0.117489 -0.00254,-0.379138 -0.00254,-0.26165 -0.00254,-0.390569 z m 0.5004366,-0.142256 h 0.043185 q 0.060332,0 0.097166,-0.02985 0.037469,-0.03048 0.037469,-0.08002 0,-0.06224 -0.025403,-0.09209 -0.025403,-0.03048 -0.09018,-0.03048 -0.033024,0 -0.060967,0.0019 -0.00127,0.148607 -0.00127,0.230532 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4952" + inkscape:connector-curvature="0" /> + d="m 9.9380235,25.299187 q 0,-0.35564 0.010161,-0.762086 0.1206635,-0.0057 0.2095735,-0.0057 0.07557,0 0.219735,0.0057 0.01842,6.35e-4 0.02794,0.0254 l 0.26292,0.650314 -0.0089,-0.675717 q 0.121299,-0.0057 0.222275,-0.0057 0.09907,0 0.234977,0.0057 0.0108,6.35e-4 0.01778,0.0083 0.0076,0.007 0.0076,0.01715 0.01016,0.477574 0.01016,0.736683 0,0.355641 -0.01016,0.762087 -0.105422,0.0057 -0.196873,0.0057 -0.108597,0 -0.232436,-0.0057 -0.01905,-6.35e-4 -0.02794,-0.0254 l -0.26292,-0.650314 0.0089,0.675717 q -0.120663,0.0057 -0.209573,0.0057 -0.100342,0 -0.2476786,-0.0057 -0.010796,-6.35e-4 -0.018417,-0.0076 -0.00699,-0.0076 -0.00699,-0.01778 -0.010161,-0.477574 -0.010161,-0.736684 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:digitalt;-inkscape-font-specification:digitalt;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4954" + inkscape:connector-curvature="0" /> + + + + + + + + + + + + + + + diff --git a/resources/graphics/badge_broken.svg b/resources/graphics/badge_broken.svg index a1d7dbc3c..31eea29f1 100644 --- a/resources/graphics/badge_broken.svg +++ b/resources/graphics/badge_broken.svg @@ -10,8 +10,8 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="80" - height="112" - viewBox="0 0 21.166666 29.633334" + height="92" + viewBox="0 0 21.166666 24.341667" version="1.1" id="svg4842" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" @@ -25,9 +25,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="9.5782066" - inkscape:cx="1.1766084" - inkscape:cy="50.443327" + inkscape:zoom="7.4483451" + inkscape:cx="-60.233009" + inkscape:cy="71.400581" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" @@ -49,7 +49,7 @@ image/svg+xml - + @@ -57,17 +57,17 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-1.9829021e-4,-266.11715)"> + transform="translate(-1.9829021e-4,-271.40882)"> + height="24.341667" + x="0.0001986081" + y="271.40881" /> + transform="matrix(0.10079384,0,0,0.0816046,-2.2531965e-4,271.54688)"> - - - - - - - - - - - - - - - - - - - - - - - + y="276.70892" + x="1.8134596" /> diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg index a09e55f4c..cbab8a44a 100644 --- a/resources/graphics/badge_completed.svg +++ b/resources/graphics/badge_completed.svg @@ -7,10 +7,10 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="80.000008" - height="112" + width="80" + height="92" version="1.1" - viewBox="0 0 21.166668 29.633334" + viewBox="0 0 21.166666 24.341667" id="svg92" sodipodi:docname="badge_completed.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> @@ -33,9 +33,9 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - inkscape:zoom="12.442155" - inkscape:cx="-34.776172" - inkscape:cy="67.39851" + inkscape:zoom="5.4859697" + inkscape:cx="-52.049045" + inkscape:cy="47.629762" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -49,20 +49,20 @@ image/svg+xml - + + height="24.341667" + x="2.2368853e-07" + y="-2.6645353e-15" /> + transform="matrix(0.10079384,0,0,0.0816046,-4.2317622e-4,0.13809161)"> - - - - - - - - - - - - - - - - - - - - - - - + y="5.3001137" + x="1.8323119" /> + transform="matrix(0.09207517,0,0,0.11015111,-4.2465753e-4,-1.9389228e-4)"> diff --git a/resources/graphics/badge_favorite.svg b/resources/graphics/badge_favorite.svg index 4b8236cae..1b723f5d7 100644 --- a/resources/graphics/badge_favorite.svg +++ b/resources/graphics/badge_favorite.svg @@ -8,9 +8,9 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="80" - height="112" + height="92" version="1.1" - viewBox="0 0 21.166666 29.633332" + viewBox="0 0 21.166666 24.341666" id="svg90" sodipodi:docname="badge_favorite.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> @@ -30,9 +30,9 @@ id="namedview92" showgrid="false" units="px" - inkscape:zoom="5.4859695" - inkscape:cx="-51.316674" - inkscape:cy="71.543477" + inkscape:zoom="2.8284271" + inkscape:cx="-170.96844" + inkscape:cy="39.989024" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -46,20 +46,20 @@ image/svg+xml - + + y="-2.6253017e-14" /> + transform="matrix(0.10079384,0,0,0.0816046,-4.2392733e-4,0.13809204)"> + d="m 17.240062,12.456484 -4.709693,-0.491108 -1.927985,-4.2699571 -1.9279844,4.2699571 -4.7096929,0.491108 3.5178058,3.130612 -0.9820949,4.573603 4.1020064,-2.335917 4.102007,2.335917 -0.982095,-4.573603 z" /> + transform="matrix(0.10111638,0,0,0.09920503,-4.2392733e-4,-1.9532637e-4)"> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/graphics/badge_kidgame.svg b/resources/graphics/badge_kidgame.svg index 565806a74..464bf61d9 100644 --- a/resources/graphics/badge_kidgame.svg +++ b/resources/graphics/badge_kidgame.svg @@ -7,10 +7,10 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="80.000008" - height="112" + width="80" + height="92" version="1.1" - viewBox="0 0 21.166668 29.633334" + viewBox="0 0 21.166666 24.341667" id="svg90" sodipodi:docname="badge_kidgame.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> @@ -33,9 +33,9 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - inkscape:zoom="8.2057888" - inkscape:cx="-57.797153" - inkscape:cy="68.015272" + inkscape:zoom="5.1167374" + inkscape:cx="-67.47745" + inkscape:cy="38.966505" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -49,20 +49,20 @@ image/svg+xml - + + height="24.341667" + x="2.2368853e-07" + y="-2.6645353e-15" /> + transform="matrix(0.10079384,0,0,0.0816046,-4.237873e-4,0.13809261)"> - - - - - - - - - - - - - - - - - - - - - - - + y="5.3001156" + x="1.8132613" /> + d="M 4.2719194,9.2899832 7.5642988,20.858601 16.894338,18.490114 16.025104,15.486921 10.530737,16.910518 10.155183,15.577444 15.650846,14.180192 14.791009,11.17484 9.310487,12.587467 8.9381592,11.320213 14.438408,9.9434334 13.590192,6.9470637 Z" /> + transform="matrix(0.09733762,0,0,0.10419427,-4.2465753e-4,-1.9389228e-4)"> right - 0.8125 0.675 - 0.15 0.21 + 0.815 0.675 + 0.13 0.1635 0 0 left 3 From 050dccb6b85646864f4ebdbcd8600f3fab3ba655 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 15 Oct 2021 20:31:51 +0200 Subject: [PATCH 065/128] Fixed an issue with international characters getting clipped in ScrollableContainer. --- es-core/src/components/ScrollableContainer.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index 51f33f917..5568d0f63 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -82,10 +82,12 @@ void ScrollableContainer::update(int deltaTime) float lineSpacing{mChildren.front()->getLineSpacing()}; float combinedHeight{mChildren.front()->getFont()->getHeight(lineSpacing)}; - // Calculate the line spacing which will be used to clip the container. - if (mClipSpacing == 0.0f) - mClipSpacing = - std::round((combinedHeight - mChildren.front()->getFont()->getLetterHeight()) / 2.0f); + // Calculate the spacing which will be used to clip the container. + if (lineSpacing > 1.2f && mClipSpacing == 0.0f) { + const float minimumSpacing = mChildren.front()->getFont()->getHeight(1.2f); + const float currentSpacing = mChildren.front()->getFont()->getHeight(lineSpacing); + mClipSpacing = std::round((currentSpacing - minimumSpacing) / 2.0f); + } // Resize container to font height boundary to avoid rendering a fraction of the last line. if (!mUpdatedSize && contentSize.y > mSize.y) { @@ -176,13 +178,13 @@ void ScrollableContainer::render(const glm::mat4& parentTrans) dimScaled.x = std::fabs(trans[3].x + mSize.x); dimScaled.y = std::fabs(trans[3].y + mSize.y); - glm::ivec2 clipDim{static_cast(dimScaled.x - trans[3].x), - static_cast(dimScaled.y - trans[3].y)}; + glm::ivec2 clipDim{static_cast(ceilf(dimScaled.x - trans[3].x)), + static_cast(ceilf(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. - clipPos.y += mClipSpacing; - clipDim.y -= mClipSpacing * 0.9f; + clipPos.y += static_cast(mClipSpacing); + clipDim.y -= static_cast(mClipSpacing); Renderer::pushClipRect(clipPos, clipDim); From 36838660626d4019f41fd907ecef93d6908364a2 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 15 Oct 2021 20:58:40 +0200 Subject: [PATCH 066/128] Improved the layout of the scraper GUIs. Also added the scroll indicators and replaced a text margin hack with a proper solution. --- es-app/src/guis/GuiGameScraper.cpp | 85 ++++++++++++++++++------ es-app/src/guis/GuiGameScraper.h | 5 ++ es-app/src/guis/GuiMetaDataEd.cpp | 8 ++- es-app/src/guis/GuiScraperMulti.cpp | 81 +++++++++++++++++----- es-app/src/guis/GuiScraperMulti.h | 5 ++ es-app/src/guis/GuiScraperSearch.cpp | 51 +++++++------- es-app/src/guis/GuiScraperSearch.h | 7 ++ es-core/src/components/ComponentList.h | 7 ++ es-core/src/components/TextComponent.cpp | 11 ++- es-core/src/components/TextComponent.h | 4 +- 10 files changed, 191 insertions(+), 73 deletions(-) diff --git a/es-app/src/guis/GuiGameScraper.cpp b/es-app/src/guis/GuiGameScraper.cpp index 9ddc85caa..bcda1e93f 100644 --- a/es-app/src/guis/GuiGameScraper.cpp +++ b/es-app/src/guis/GuiGameScraper.cpp @@ -23,15 +23,13 @@ GuiGameScraper::GuiGameScraper(Window* window, std::function doneFunc) : GuiComponent(window) , mClose(false) - , mGrid(window, glm::ivec2{1, 7}) + , mGrid(window, glm::ivec2{2, 6}) , mBox(window, ":/graphics/frame.svg") , mSearchParams(params) { addChild(&mBox); addChild(&mGrid); - // Row 0 is a spacer. - std::string scrapeName; if (Settings::getInstance()->getBool("ScraperSearchMetadataName")) { @@ -51,21 +49,37 @@ GuiGameScraper::GuiGameScraper(Window* window, mWindow, scrapeName + ((mSearchParams.game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR : ""), - Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); - mGrid.setEntry(mGameName, glm::ivec2{0, 1}, false, true); - - // Row 2 is a spacer. + Font::get(FONT_SIZE_LARGE), 0x777777FF, ALIGN_CENTER); + mGameName->setColor(0x555555FF); + mGrid.setEntry(mGameName, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2}); mSystemName = std::make_shared( mWindow, Utils::String::toUpper(mSearchParams.system->getFullName()), Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER); - mGrid.setEntry(mSystemName, glm::ivec2{0, 3}, false, true); + mGrid.setEntry(mSystemName, glm::ivec2{0, 2}, false, true, glm::ivec2{2, 1}); - // Row 4 is a spacer. + // Row 3 is a spacer. // GuiScraperSearch. mSearch = std::make_shared(window, GuiScraperSearch::NEVER_AUTO_ACCEPT, 1); - mGrid.setEntry(mSearch, glm::ivec2{0, 5}, true); + mGrid.setEntry(mSearch, glm::ivec2{0, 4}, true, true, glm::ivec2{2, 1}); + + mResultList = mSearch->getResultList(); + + // Set up scroll indicators. + mScrollUp = std::make_shared(mWindow); + mScrollDown = std::make_shared(mWindow); + mScrollIndicator = + std::make_shared(mResultList, mScrollUp, mScrollDown); + + mScrollUp->setResize(0.0f, mGameName->getFont()->getLetterHeight() / 2.0f); + mScrollUp->setOrigin(0.0f, -0.35f); + + mScrollDown->setResize(0.0f, mGameName->getFont()->getLetterHeight() / 2.0f); + mScrollDown->setOrigin(0.0f, 0.35f); + + mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1}); // Buttons std::vector> buttons; @@ -74,6 +88,9 @@ GuiGameScraper::GuiGameScraper(Window* window, std::make_shared(mWindow, "REFINE SEARCH", "refine search", [&] { // Refine the search, unless the result has already been accepted. if (!mSearch->getAcceptedResult()) { + // Copy any search refine that may have been previously entered by opening + // the input screen using the "Y" button shortcut. + mSearchParams.nameOverride = mSearch->getNameOverride(); mSearch->openInputScreen(mSearchParams); mGrid.resetCursor(); } @@ -92,20 +109,34 @@ GuiGameScraper::GuiGameScraper(Window* window, })); mButtonGrid = makeButtonGrid(mWindow, buttons); - mGrid.setEntry(mButtonGrid, glm::ivec2{0, 6}, true, false); + mGrid.setEntry(mButtonGrid, glm::ivec2{0, 5}, true, false, glm::ivec2{2, 1}); mSearch->setAcceptCallback([this, doneFunc](const ScraperSearchResult& result) { doneFunc(result); close(); }); mSearch->setCancelCallback([&] { delete this; }); + mSearch->setRefineCallback([&] { + mScrollUp->setOpacity(0); + mScrollDown->setOpacity(0); + mResultList->resetScrollIndicatorStatus(); + }); // Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is // the 16:9 reference. float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); float width = glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth(); - setSize(width, Renderer::getScreenHeight() * 0.747f); + float height = (mGameName->getFont()->getLetterHeight() + + static_cast(Renderer::getScreenHeight()) * 0.0637f) + + mSystemName->getFont()->getLetterHeight() + + static_cast(Renderer::getScreenHeight()) * 0.04f + + mButtonGrid->getSize().y + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f; + + // TODO: Temporary hack, see below. + height -= 7.0f * Renderer::getScreenHeightModifier(); + + setSize(width, height); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); @@ -115,15 +146,31 @@ GuiGameScraper::GuiGameScraper(Window* window, void GuiGameScraper::onSizeChanged() { + mGrid.setRowHeightPerc( + 0, (mGameName->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) / + mSize.y / 2.0f); + mGrid.setRowHeightPerc( + 1, (mGameName->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) / + mSize.y / 2.0f); + mGrid.setRowHeightPerc(2, mSystemName->getFont()->getLetterHeight() / mSize.y, false); + mGrid.setRowHeightPerc(3, 0.04f, false); + mGrid.setRowHeightPerc(4, ((Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f)) / mSize.y, false); + + // TODO: Replace this temporary hack with a proper solution. There is some kind of rounding + // issue somewhere that causes a small alignment error. This code partly compensates for this + // at higher resolutions than 1920x1080. + if (Renderer::getScreenHeightModifier() > 1.0f) + mSize.y -= 3.0f * Renderer::getScreenHeightModifier(); + + mGrid.setColWidthPerc(1, 0.04f); + + mGrid.setSize(mSize); mBox.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); - mGrid.setRowHeightPerc(0, 0.04f, false); - mGrid.setRowHeightPerc(1, mGameName->getFont()->getLetterHeight() / mSize.y, false); - mGrid.setRowHeightPerc(2, 0.04f, false); - mGrid.setRowHeightPerc(3, mSystemName->getFont()->getLetterHeight() / mSize.y, false); - mGrid.setRowHeightPerc(4, 0.04f, false); - mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y / mSize.y, false); - mGrid.setSize(mSize); + // Add some extra margins to the game name. + const float newSizeX = mSize.x * 0.96f; + mGameName->setSize(newSizeX, mGameName->getSize().y); + mGameName->setPosition((mSize.x - newSizeX) / 2.0f, 0.0f); } bool GuiGameScraper::input(InputConfig* config, Input input) diff --git a/es-app/src/guis/GuiGameScraper.h b/es-app/src/guis/GuiGameScraper.h index b910c1351..040f1e072 100644 --- a/es-app/src/guis/GuiGameScraper.h +++ b/es-app/src/guis/GuiGameScraper.h @@ -13,6 +13,7 @@ #include "GuiComponent.h" #include "components/NinePatchComponent.h" +#include "components/ScrollIndicatorComponent.h" #include "guis/GuiScraperSearch.h" class GuiGameScraper : public GuiComponent @@ -38,9 +39,13 @@ private: NinePatchComponent mBox; std::shared_ptr mGameName; + std::shared_ptr mScrollUp; + std::shared_ptr mScrollDown; + std::shared_ptr mScrollIndicator; std::shared_ptr mSystemName; std::shared_ptr mSearch; std::shared_ptr mButtonGrid; + std::shared_ptr mResultList; ScraperSearchParams mSearchParams; diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 594b55fe6..5a945c640 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -80,8 +80,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, folderPath + Utils::FileSystem::getFileName(scraperParams.game->getPath()) + " [" + Utils::String::toUpper(scraperParams.system->getName()) + "]" + (scraperParams.game->getType() == FOLDER ? " " + ViewController::FOLDER_CHAR : ""), - Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER, glm::vec3{}, glm::vec2{}, 0x00000000, - 0.05f); + Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER); mGrid.setEntry(mSubtitle, glm::ivec2{0, 2}, false, true, glm::ivec2{2, 1}); @@ -505,6 +504,11 @@ void GuiMetaDataEd::onSizeChanged() setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); + + // Add some extra margins to the file/folder name. + const float newSizeX = mSize.x * 0.96f; + mSubtitle->setSize(newSizeX, mSubtitle->getSize().y); + mSubtitle->setPosition((mSize.x - newSizeX) / 2.0f, mSubtitle->getPosition().y); } void GuiMetaDataEd::save() diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp index 067352199..50b8aab67 100644 --- a/es-app/src/guis/GuiScraperMulti.cpp +++ b/es-app/src/guis/GuiScraperMulti.cpp @@ -28,7 +28,7 @@ GuiScraperMulti::GuiScraperMulti(Window* window, bool approveResults) : GuiComponent(window) , mBackground(window, ":/graphics/frame.svg") - , mGrid(window, glm::ivec2{1, 5}) + , mGrid(window, glm::ivec2{2, 6}) , mSearchQueue(searches) , mApproveResults(approveResults) { @@ -47,15 +47,15 @@ GuiScraperMulti::GuiScraperMulti(Window* window, // Set up grid. mTitle = std::make_shared(mWindow, "SCRAPING IN PROGRESS", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); - mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true); + mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2}); mSystem = std::make_shared(mWindow, "SYSTEM", Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); - mGrid.setEntry(mSystem, glm::ivec2{0, 1}, false, true); + mGrid.setEntry(mSystem, glm::ivec2{0, 2}, false, true, glm::ivec2{2, 1}); mSubtitle = std::make_shared( mWindow, "subtitle text", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER); - mGrid.setEntry(mSubtitle, glm::ivec2{0, 2}, false, true); + mGrid.setEntry(mSubtitle, glm::ivec2{0, 3}, false, true, glm::ivec2{2, 1}); if (mApproveResults && !Settings::getInstance()->getBool("ScraperSemiautomatic")) mSearchComp = std::make_shared( @@ -70,10 +70,34 @@ GuiScraperMulti::GuiScraperMulti(Window* window, std::bind(&GuiScraperMulti::acceptResult, this, std::placeholders::_1)); mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this)); mSearchComp->setCancelCallback(std::bind(&GuiScraperMulti::finish, this)); - mGrid.setEntry(mSearchComp, glm::ivec2{0, 3}, - mSearchComp->getSearchType() != GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT, - true); + mSearchComp->setRefineCallback([&] { + mScrollUp->setOpacity(0); + mScrollDown->setOpacity(0); + mResultList->resetScrollIndicatorStatus(); + }); + mGrid.setEntry(mSearchComp, glm::ivec2{0, 4}, + mSearchComp->getSearchType() != GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT, + true, glm::ivec2{2, 1}); + + mResultList = mSearchComp->getResultList(); + + // Set up scroll indicators. + mScrollUp = std::make_shared(mWindow); + mScrollDown = std::make_shared(mWindow); + mScrollIndicator = + std::make_shared(mResultList, mScrollUp, mScrollDown); + + mScrollUp->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f); + mScrollUp->setOrigin(0.0f, -0.35f); + + mScrollDown->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f); + mScrollDown->setOrigin(0.0f, 0.35f); + + mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1}); + mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1}); + + // Buttons. std::vector> buttons; if (mApproveResults) { @@ -125,14 +149,23 @@ GuiScraperMulti::GuiScraperMulti(Window* window, std::bind(&GuiScraperMulti::finish, this))); mButtonGrid = makeButtonGrid(mWindow, buttons); - mGrid.setEntry(mButtonGrid, glm::ivec2{0, 4}, true, false); + mGrid.setEntry(mButtonGrid, glm::ivec2{0, 5}, true, false, glm::ivec2{2, 1}); // Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is // the 16:9 reference. float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); float width = glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth(); - setSize(width, Renderer::getScreenHeight() * 0.849f); + float height = (mTitle->getFont()->getLetterHeight() + + static_cast(Renderer::getScreenHeight()) * 0.0637f) + + mSystem->getFont()->getLetterHeight() + + mSubtitle->getFont()->getHeight() * 1.75f + mButtonGrid->getSize().y + + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 7.0f; + + // TODO: Temporary hack, see below. + height -= 7.0f * Renderer::getScreenHeightModifier(); + + setSize(width, height); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, (Renderer::getScreenHeight() - mSize.y) / 2.0f); @@ -153,13 +186,26 @@ GuiScraperMulti::~GuiScraperMulti() void GuiScraperMulti::onSizeChanged() { - mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); + mGrid.setRowHeightPerc( + 0, (mTitle->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) / + mSize.y / 2.0f); + mGrid.setRowHeightPerc( + 1, (mTitle->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) / + mSize.y / 2.0f); + mGrid.setRowHeightPerc(2, (mSystem->getFont()->getLetterHeight()) / mSize.y, false); + mGrid.setRowHeightPerc(3, mSubtitle->getFont()->getHeight() * 1.75f / mSize.y, false); + mGrid.setRowHeightPerc(4, ((Font::get(FONT_SIZE_MEDIUM)->getHeight() * 7.0f)) / mSize.y, false); + + // TODO: Replace this temporary hack with a proper solution. There is some kind of rounding + // issue somewhere that causes a small alignment error. This code partly compensates for this + // at higher resolutions than 1920x1080. + if (Renderer::getScreenHeightModifier() > 1.0f) + mSize.y -= 3.0f * Renderer::getScreenHeightModifier(); + + mGrid.setColWidthPerc(1, 0.04f); - mGrid.setRowHeightPerc(0, mTitle->getFont()->getLetterHeight() * 1.9725f / mSize.y, false); - mGrid.setRowHeightPerc(1, (mSystem->getFont()->getLetterHeight() + 2.0f) / mSize.y, false); - mGrid.setRowHeightPerc(2, mSubtitle->getFont()->getHeight() * 1.75f / mSize.y, false); - mGrid.setRowHeightPerc(4, mButtonGrid->getSize().y / mSize.y, false); mGrid.setSize(mSize); + mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); } void GuiScraperMulti::doNextSearch() @@ -189,6 +235,10 @@ void GuiScraperMulti::doNextSearch() scrapeName = Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath()); } + mScrollUp->setOpacity(0); + mScrollDown->setOpacity(0); + mResultList->resetScrollIndicatorStatus(); + // Extract possible subfolders from the path. std::string folderPath = Utils::String::replace(Utils::FileSystem::getParent(mSearchQueue.front().game->getPath()), @@ -264,9 +314,6 @@ void GuiScraperMulti::finish() std::vector GuiScraperMulti::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); - // Remove the 'Choose' entry if in fully automatic mode. - if (!mApproveResults) - prompts.pop_back(); return prompts; } diff --git a/es-app/src/guis/GuiScraperMulti.h b/es-app/src/guis/GuiScraperMulti.h index 63c755f6b..d71525086 100644 --- a/es-app/src/guis/GuiScraperMulti.h +++ b/es-app/src/guis/GuiScraperMulti.h @@ -16,6 +16,7 @@ #include "MetaData.h" #include "components/ComponentGrid.h" #include "components/NinePatchComponent.h" +#include "components/ScrollIndicatorComponent.h" #include "scrapers/Scraper.h" class GuiScraperSearch; @@ -45,10 +46,14 @@ private: ComponentGrid mGrid; std::shared_ptr mTitle; + std::shared_ptr mScrollUp; + std::shared_ptr mScrollDown; + std::shared_ptr mScrollIndicator; std::shared_ptr mSystem; std::shared_ptr mSubtitle; std::shared_ptr mSearchComp; std::shared_ptr mButtonGrid; + std::shared_ptr mResultList; std::queue mSearchQueue; std::vector mMetaDataDecl; diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 340eb81e2..6a8709c35 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -39,7 +39,7 @@ GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int scrapeCount) : GuiComponent(window) - , mGrid(window, glm::ivec2{4, 3}) + , mGrid(window, glm::ivec2{5, 3}) , mSearchType(type) , mScrapeCount(scrapeCount) , mRefinedSearch(false) @@ -88,14 +88,11 @@ GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int mMD_ReleaseDate = std::make_shared(mWindow); mMD_ReleaseDate->setColor(mdColor); mMD_ReleaseDate->setUppercase(true); - mMD_Developer = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, - glm::vec3{}, glm::vec2{}, 0x00000000, 0.02f); - mMD_Publisher = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, - glm::vec3{}, glm::vec2{}, 0x00000000, 0.02f); - mMD_Genre = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, glm::vec3{}, - glm::vec2{}, 0x00000000, 0.02f); - mMD_Players = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, - glm::vec3{}, glm::vec2{}, 0x00000000, 0.02f); + mMD_Developer = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT); + mMD_Publisher = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT); + mMD_Genre = + std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT, glm::vec3{}); + mMD_Players = std::make_shared(mWindow, "", font, mdColor, ALIGN_LEFT); mMD_Filler = std::make_shared(mWindow, "", font, mdColor); if (Settings::getInstance()->getString("Scraper") != "thegamesdb") @@ -193,45 +190,47 @@ void GuiScraperSearch::onSizeChanged() mGrid.setColWidthPerc(1, 0.25f); if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) - mGrid.setColWidthPerc(2, 0.25f); + mGrid.setColWidthPerc(2, 0.33f); else - mGrid.setColWidthPerc(2, 0.28f); + mGrid.setColWidthPerc(2, 0.30f); // Row heights. if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) // Show name. mGrid.setRowHeightPerc(0, (mResultName->getFont()->getHeight() * 1.6f) / mGrid.getSize().y); // Result name. else - mGrid.setRowHeightPerc(0, 0.0825f); // Hide name but do padding. + mGrid.setRowHeightPerc(0, 0.0725f); // Hide name but do padding. if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) mGrid.setRowHeightPerc(2, 0.2f); else mGrid.setRowHeightPerc(1, 0.505f); - const float boxartCellScale = 0.9f; + const float thumbnailCellScale = 0.93f; // Limit thumbnail size using setMaxHeight - we do this instead of letting mGrid // call setSize because it maintains the aspect ratio. // We also pad a little so it doesn't rub up against the metadata labels. - mResultThumbnail->setMaxSize(mGrid.getColWidth(1) * boxartCellScale, mGrid.getRowHeight(1)); + mResultThumbnail->setMaxSize(mGrid.getColWidth(1) * thumbnailCellScale, mGrid.getRowHeight(1)); // Metadata. resizeMetadata(); + // Small vertical spacer between the metadata fields and the result list. + mGrid.setColWidthPerc(3, 0.004f); + if (mSearchType != ALWAYS_ACCEPT_FIRST_RESULT) - mDescContainer->setSize(mGrid.getColWidth(1) * boxartCellScale + mGrid.getColWidth(2), + mDescContainer->setSize(mGrid.getColWidth(1) * thumbnailCellScale + mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3.0f); else - mDescContainer->setSize(mGrid.getColWidth(3) * boxartCellScale, + mDescContainer->setSize(mGrid.getColWidth(4) * thumbnailCellScale, mResultDesc->getFont()->getHeight() * 6.0f); // Make description text wrap at edge of container. mResultDesc->setSize(mDescContainer->getSize().x, 0.0f); // Set the width of mResultName to the cell width so that text abbreviation will work correctly. - glm::vec2 resultNameSize{mResultName->getSize()}; - mResultName->setSize(mGrid.getColWidth(3), resultNameSize.y); + mResultName->setSize(mGrid.getColWidth(1) + mGrid.getColWidth(2), mResultName->getSize().y); mGrid.onSizeChanged(); mBusyAnim.setSize(mSize); @@ -289,30 +288,30 @@ void GuiScraperSearch::updateViewStyle() // Add them back depending on search type. if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) { // Show name. - mGrid.setEntry(mResultName, glm::ivec2{1, 0}, false, false, glm::ivec2{2, 1}, + mGrid.setEntry(mResultName, glm::ivec2{1, 0}, false, false, glm::ivec2{3, 1}, GridFlags::BORDER_TOP); // Need a border on the bottom left. mGrid.setEntry(std::make_shared(mWindow), glm::ivec2{0, 2}, false, false, - glm::ivec2{3, 1}, GridFlags::BORDER_BOTTOM); + glm::ivec2{4, 1}, GridFlags::BORDER_BOTTOM); // Show description on the right. - mGrid.setEntry(mDescContainer, glm::ivec2{3, 0}, false, false, glm::ivec2{1, 3}, - GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM); + mGrid.setEntry(mDescContainer, glm::ivec2{4, 0}, false, false, glm::ivec2{1, 3}, + GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM | GridFlags::BORDER_LEFT); // Make description text wrap at edge of container. mResultDesc->setSize(mDescContainer->getSize().x, 0.0f); } else { // Fake row where name would be. mGrid.setEntry(std::make_shared(mWindow), glm::ivec2{1, 0}, false, true, - glm::ivec2{2, 1}, GridFlags::BORDER_TOP); + glm::ivec2{3, 1}, GridFlags::BORDER_TOP); // Show result list on the right. - mGrid.setEntry(mResultList, glm::ivec2{3, 0}, true, true, glm::ivec2{1, 3}, + mGrid.setEntry(mResultList, glm::ivec2{4, 0}, true, true, glm::ivec2{1, 3}, GridFlags::BORDER_LEFT | GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM); // Show description under image/info. - mGrid.setEntry(mDescContainer, glm::ivec2{1, 2}, false, false, glm::ivec2{2, 1}, + mGrid.setEntry(mDescContainer, glm::ivec2{1, 2}, false, false, glm::ivec2{3, 1}, GridFlags::BORDER_BOTTOM); // Make description text wrap at edge of container. mResultDesc->setSize(mDescContainer->getSize().x, 0); @@ -798,6 +797,8 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) stop(); mRefinedSearch = true; params.nameOverride = name; + if (mRefineCallback != nullptr) + mRefineCallback(); search(params); }; diff --git a/es-app/src/guis/GuiScraperSearch.h b/es-app/src/guis/GuiScraperSearch.h index 449124ba0..082e8e3b0 100644 --- a/es-app/src/guis/GuiScraperSearch.h +++ b/es-app/src/guis/GuiScraperSearch.h @@ -71,6 +71,10 @@ public: { mCancelCallback = cancelCallback; } + void setRefineCallback(const std::function& refineCallback) + { + mRefineCallback = refineCallback; + } bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; @@ -92,6 +96,8 @@ public: void onFocusGained() override { mGrid.onFocusGained(); } void onFocusLost() override { mGrid.onFocusLost(); } + std::shared_ptr& getResultList() { return mResultList; } + private: void updateViewStyle(); void updateThumbnail(); @@ -152,6 +158,7 @@ private: std::function mAcceptCallback; std::function mSkipCallback; std::function mCancelCallback; + std::function mRefineCallback; unsigned int mScrapeCount; bool mRefinedSearch; bool mBlockAccept; diff --git a/es-core/src/components/ComponentList.h b/es-core/src/components/ComponentList.h index 9035cd881..fd7739fc5 100644 --- a/es-core/src/components/ComponentList.h +++ b/es-core/src/components/ComponentList.h @@ -86,6 +86,13 @@ public: float getTotalRowHeight() const; float getRowHeight(int row) const { return getRowHeight(mEntries.at(row).data); } + void resetScrollIndicatorStatus() + { + mScrollIndicatorStatus = SCROLL_NONE; + if (mScrollIndicatorChangedCallback != nullptr) + mScrollIndicatorChangedCallback(mScrollIndicatorStatus); + } + void setCursorChangedCallback(const std::function& callback) { mCursorChangedCallback = callback; diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index fa15ce628..08aa17674 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -17,7 +17,6 @@ TextComponent::TextComponent(Window* window) , mFont{Font::get(FONT_SIZE_MEDIUM)} , mColor{0x000000FF} , mBgColor{0} - , mMargin{0.0f} , mRenderBackground{false} , mUppercase{false} , mAutoCalcExtent{1, 1} @@ -36,13 +35,11 @@ TextComponent::TextComponent(Window* window, Alignment align, glm::vec3 pos, glm::vec2 size, - unsigned int bgcolor, - float margin) + unsigned int bgcolor) : GuiComponent{window} , mFont{nullptr} , mColor{0x000000FF} , mBgColor{0} - , mMargin{margin} , mRenderBackground{false} , mUppercase{false} , mAutoCalcExtent{1, 1} @@ -238,12 +235,12 @@ void TextComponent::onTextChanged() // Abbreviate text. const std::string abbrev = "..."; glm::vec2 abbrevSize{f->sizeText(abbrev)}; - // mMargin adds a margin around the text if it's abbreviated. - float marginAdjustedSize = mSize.x - (mSize.x * mMargin); - while (text.size() && size.x + abbrevSize.x > marginAdjustedSize) { + while (text.size() && size.x + abbrevSize.x > 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); } diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index cd07f5592..eb1694cdc 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -32,8 +32,7 @@ public: Alignment align = ALIGN_LEFT, glm::vec3 pos = {}, glm::vec2 size = {}, - unsigned int bgcolor = 0x00000000, - float margin = 0.0f); + unsigned int bgcolor = 0x00000000); void setFont(const std::shared_ptr& font); void setUppercase(bool uppercase); @@ -89,7 +88,6 @@ private: unsigned int mBgColor; unsigned char mColorOpacity; unsigned char mBgColorOpacity; - float mMargin; bool mRenderBackground; bool mUppercase; From 97af891b664d495b1baa430f22322821d3eb1172 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 15 Oct 2021 21:21:49 +0200 Subject: [PATCH 067/128] Increased the game description row count for the automatic multi-scraper. --- es-app/src/guis/GuiScraperSearch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 6a8709c35..14da96239 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -224,7 +224,7 @@ void GuiScraperSearch::onSizeChanged() mResultDesc->getFont()->getHeight() * 3.0f); else mDescContainer->setSize(mGrid.getColWidth(4) * thumbnailCellScale, - mResultDesc->getFont()->getHeight() * 6.0f); + mResultDesc->getFont()->getHeight() * 8.0f); // Make description text wrap at edge of container. mResultDesc->setSize(mDescContainer->getSize().x, 0.0f); From c68f78f3d16ca6da4479afaba7c179072a60e905 Mon Sep 17 00:00:00 2001 From: shadash Date: Fri, 15 Oct 2021 21:28:12 +0200 Subject: [PATCH 068/128] fix right align Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index f9b81abd9..f448fbc16 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -166,10 +166,9 @@ void FlexboxComponent::computeLayout() image.second.setSize(image.second.getSize().x, maxItemSize.y); } - // TODO: Doesn't work correctly. // Apply overall container alignment. if (mAlignment == "right") - x += (mSize.x - size.x * grid.x) - mItemMargin.x; + x += (mSize.x - maxItemSize.x * grid.x - (grid.x - 1) * mItemMargin.x); // Store final item position. image.second.setPosition(x, y); @@ -193,5 +192,22 @@ void FlexboxComponent::computeLayout() } } + // Apply right-align + if (mAlignment == "right") { + unsigned int n = mItemsPerLine - (--i + 1) % std::max(1, static_cast(mItemsPerLine)); + i = 0; + unsigned int line = 1; + for (auto& image : mImages) { + if (!image.second.isVisible()) + continue; + if (line == mLines) + image.second.setPosition(image.second.getPosition().x + + (maxItemSize.x + mItemMargin.x) * n, + image.second.getPosition().y); + if ((i++ + 1) % std::max(1, static_cast(mItemsPerLine)) == 0) + line++; + } + } + mLayoutValid = true; } From 1c93ca2c0759fc2a5d067b9bf4b3a08d33d70cbf Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Fri, 15 Oct 2021 21:33:34 +0200 Subject: [PATCH 069/128] fix right align Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index f448fbc16..a31871102 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -201,9 +201,10 @@ void FlexboxComponent::computeLayout() if (!image.second.isVisible()) continue; if (line == mLines) - image.second.setPosition(image.second.getPosition().x + - (maxItemSize.x + mItemMargin.x) * n, - image.second.getPosition().y); + image.second.setPosition( + image.second.getPosition().x + + floorf((maxItemSize.x + mItemMargin.x) * static_cast(n)), + image.second.getPosition().y); if ((i++ + 1) % std::max(1, static_cast(mItemsPerLine)) == 0) line++; } From aa8b68f2a9a08b0350fe9b49db130142092ca5bf Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 15 Oct 2021 22:35:57 +0200 Subject: [PATCH 070/128] Set the menu scroll indicators as enabled by default. --- es-core/src/Settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index a69705ea9..e7035633c 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -181,7 +181,7 @@ void Settings::setDefaults() mBoolMap["SpecialCharsASCII"] = {false, false}; mBoolMap["ListScrollOverlay"] = {false, false}; mBoolMap["VirtualKeyboard"] = {true, true}; - mBoolMap["ScrollIndicators"] = {false, false}; + mBoolMap["ScrollIndicators"] = {true, true}; mBoolMap["FavoritesAddButton"] = {true, true}; mBoolMap["RandomAddButton"] = {false, false}; mBoolMap["GamelistFilters"] = {true, true}; From 4c556fc820211eab705232fa987a1f729e0b0153 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 16 Oct 2021 12:23:32 +0200 Subject: [PATCH 071/128] Changed the position of the per-game alternative emulator selector window. --- es-app/src/guis/GuiMetaDataEd.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 5a945c640..defc04f04 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -306,13 +306,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, static_cast(Renderer::getScreenWidth()) * maxWidthModifier; s->setMenuSize(glm::vec2{maxWidth, s->getMenuSize().y}); - - auto menuSize = s->getMenuSize(); - auto menuPos = s->getMenuPosition(); - - s->setMenuPosition(glm::vec3{(s->getSize().x - menuSize.x) / 2.0f, - (s->getSize().y - menuSize.y) / 3.0f, - menuPos.z}); + s->setMenuPosition(glm::vec3{(s->getSize().x - maxWidth) / 2.0f, + mPosition.y, mPosition.z}); mWindow->pushGui(s); }); } From 690083a123e4c0d4c8b78996709ec2e9a4095d0e Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 16 Oct 2021 13:21:52 +0200 Subject: [PATCH 072/128] The scroll indicators don't fade in and out any longer if quick jumping in a list. --- es-core/src/components/ComponentList.cpp | 7 +++++- es-core/src/components/ComponentList.h | 8 ++++--- .../src/components/ScrollIndicatorComponent.h | 24 ++++++++++++------- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index c829399ee..d05047e79 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -15,6 +15,7 @@ ComponentList::ComponentList(Window* window) , mFocused{false} , mSetupCompleted{false} , mBottomCameraOffset{false} + , mSingleRowScroll{false} , mSelectorBarOffset{0.0f} , mCameraOffset{0.0f} , mScrollIndicatorStatus{SCROLL_NONE} @@ -63,6 +64,8 @@ bool ComponentList::input(InputConfig* config, Input input) if (size() == 0) return false; + mSingleRowScroll = false; + if (input.value && (config->isMappedTo("a", input) || config->isMappedLike("lefttrigger", input) || config->isMappedLike("righttrigger", input))) { @@ -86,9 +89,11 @@ bool ComponentList::input(InputConfig* config, Input input) // Input handler didn't consume the input - try to scroll. if (config->isMappedLike("up", input)) { + mSingleRowScroll = true; return listInput(input.value != 0 ? -1 : 0); } else if (config->isMappedLike("down", input)) { + mSingleRowScroll = true; return listInput(input.value != 0 ? 1 : 0); } else if (config->isMappedLike("leftshoulder", input)) { @@ -142,7 +147,7 @@ void ComponentList::update(int deltaTime) } if (scrollIndicatorChanged == true && mScrollIndicatorChangedCallback != nullptr) - mScrollIndicatorChangedCallback(mScrollIndicatorStatus); + mScrollIndicatorChangedCallback(mScrollIndicatorStatus, mSingleRowScroll); listUpdate(deltaTime); diff --git a/es-core/src/components/ComponentList.h b/es-core/src/components/ComponentList.h index fd7739fc5..d1a3eee01 100644 --- a/es-core/src/components/ComponentList.h +++ b/es-core/src/components/ComponentList.h @@ -90,7 +90,7 @@ public: { mScrollIndicatorStatus = SCROLL_NONE; if (mScrollIndicatorChangedCallback != nullptr) - mScrollIndicatorChangedCallback(mScrollIndicatorStatus); + mScrollIndicatorChangedCallback(mScrollIndicatorStatus, false); } void setCursorChangedCallback(const std::function& callback) @@ -102,7 +102,7 @@ public: return mCursorChangedCallback; } void setScrollIndicatorChangedCallback( - const std::function& callback) + const std::function& callback) { mScrollIndicatorChangedCallback = callback; } @@ -114,6 +114,7 @@ private: bool mFocused; bool mSetupCompleted; bool mBottomCameraOffset; + bool mSingleRowScroll; void updateCameraOffset(); void updateElementPosition(const ComponentListRow& row); @@ -126,7 +127,8 @@ private: float mCameraOffset; std::function mCursorChangedCallback; - std::function mScrollIndicatorChangedCallback; + std::function + mScrollIndicatorChangedCallback; ScrollIndicator mScrollIndicatorStatus; }; diff --git a/es-core/src/components/ScrollIndicatorComponent.h b/es-core/src/components/ScrollIndicatorComponent.h index 4c0f5801c..58afa5b9b 100644 --- a/es-core/src/components/ScrollIndicatorComponent.h +++ b/es-core/src/components/ScrollIndicatorComponent.h @@ -34,7 +34,7 @@ public: // If the scroll indicators setting is disabled, then show a permanent down arrow // symbol when the component list contains more entries than can fit on screen. componentList.get()->setScrollIndicatorChangedCallback( - [scrollUp, scrollDown](ComponentList::ScrollIndicator state) { + [scrollUp, scrollDown](ComponentList::ScrollIndicator state, bool singleRowScroll) { if (state == ComponentList::SCROLL_UP || state == ComponentList::SCROLL_UP_DOWN || state == ComponentList::SCROLL_DOWN) { @@ -46,8 +46,9 @@ public: // If the scroll indicator setting is enabled, then also show the up and up/down // combination and switch between these as the list is scrolled. componentList.get()->setScrollIndicatorChangedCallback( - [this, scrollUp, scrollDown](ComponentList::ScrollIndicator state) { - float fadeInTime{FADE_IN_TIME}; + [this, scrollUp, scrollDown](ComponentList::ScrollIndicator state, + bool singleRowScroll) { + float fadeTime{FADE_IN_TIME}; bool upFadeIn = false; bool upFadeOut = false; @@ -68,7 +69,7 @@ public: else if (state == ComponentList::SCROLL_UP && mPreviousScrollState == ComponentList::SCROLL_DOWN) { upFadeIn = true; - fadeInTime *= 1.5f; + fadeTime *= 2.0f; scrollDown->setOpacity(0); } else if (state == ComponentList::SCROLL_UP_DOWN && @@ -95,17 +96,22 @@ public: else if (state == ComponentList::SCROLL_DOWN && mPreviousScrollState == ComponentList::SCROLL_UP) { downFadeIn = true; - fadeInTime *= 1.5f; + fadeTime *= 2.0f; scrollUp->setOpacity(0); } + // If jumping more than one row using the shoulder or trigger buttons, then + // don't fade the indicators. + if (!singleRowScroll) + fadeTime = 0.0f; + if (upFadeIn) { auto upFadeInFunc = [scrollUp](float t) { scrollUp->setOpacity( static_cast(glm::mix(0.0f, 1.0f, t) * 255)); }; scrollUp->setAnimation( - new LambdaAnimation(upFadeInFunc, static_cast(fadeInTime)), 0, + new LambdaAnimation(upFadeInFunc, static_cast(fadeTime)), 0, nullptr, false); } @@ -115,7 +121,7 @@ public: static_cast(glm::mix(0.0f, 1.0f, t) * 255)); }; scrollUp->setAnimation( - new LambdaAnimation(upFadeOutFunc, static_cast(fadeInTime)), 0, + new LambdaAnimation(upFadeOutFunc, static_cast(fadeTime)), 0, nullptr, true); } @@ -125,7 +131,7 @@ public: static_cast(glm::mix(0.0f, 1.0f, t) * 255)); }; scrollDown->setAnimation( - new LambdaAnimation(downFadeInFunc, static_cast(fadeInTime)), 0, + new LambdaAnimation(downFadeInFunc, static_cast(fadeTime)), 0, nullptr, false); } @@ -135,7 +141,7 @@ public: static_cast(glm::mix(0.0f, 1.0f, t) * 255)); }; scrollDown->setAnimation( - new LambdaAnimation(downFadeOutFunc, static_cast(fadeInTime)), 0, + new LambdaAnimation(downFadeOutFunc, static_cast(fadeTime)), 0, nullptr, true); } From 8fd05fcd77a3e747431a7d5fd90f8f2d250d41fd Mon Sep 17 00:00:00 2001 From: shadash Date: Sun, 17 Oct 2021 01:11:01 +0200 Subject: [PATCH 073/128] bugfix, simplification Signed-off-by: Sophia Hadash --- es-core/src/components/FlexboxComponent.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index a31871102..612a61bad 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -194,7 +194,8 @@ void FlexboxComponent::computeLayout() // Apply right-align if (mAlignment == "right") { - unsigned int n = mItemsPerLine - (--i + 1) % std::max(1, static_cast(mItemsPerLine)); + unsigned int m = i % std::max(1, static_cast(mItemsPerLine)); + unsigned int n = m > 0 ? mItemsPerLine - m : m; i = 0; unsigned int line = 1; for (auto& image : mImages) { From 11ca17fc9136b7fbcbded13cfce4c3c9b835d8a6 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 17 Oct 2021 16:14:28 +0200 Subject: [PATCH 074/128] Fixed an issue where the wrong scroll indicators could be displayed. --- es-core/src/components/ComponentList.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index d05047e79..31eba491a 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -208,7 +208,9 @@ void ComponentList::updateCameraOffset() i++; } - if (mCameraOffset < oldCameraOffset) + if (mCameraOffset < oldCameraOffset && + (oldCameraOffset > mSelectorBarOffset || + mScrollIndicatorStatus != ComponentList::SCROLL_NONE)) mBottomCameraOffset = false; if (mCameraOffset < 0.0f) From af1d1b310966e2cd342e989ae07e60b9acd32502 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 17 Oct 2021 18:45:21 +0200 Subject: [PATCH 075/128] Fixed some rounding issues and the right-alignment in FlexboxComponent. --- es-core/src/components/FlexboxComponent.cpp | 27 +++++++++------------ 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 612a61bad..4979de675 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -122,7 +122,7 @@ void FlexboxComponent::computeLayout() newSize = sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; if (image.second.getSize() != newSize) - image.second.setResize(newSize.x, newSize.y); + image.second.setResize(std::round(newSize.x), std::round(newSize.y)); // In case maxItemSize needs to be updated. if (newSize.x != sizeChange.x) @@ -132,9 +132,9 @@ void FlexboxComponent::computeLayout() } if (maxItemSize.x != sizeChange.x) - maxItemSize.x = sizeChange.x; + maxItemSize.x = std::round(sizeChange.x); if (maxItemSize.y != sizeChange.y) - maxItemSize.y = sizeChange.y; + maxItemSize.y = std::round(sizeChange.y); // Pre-compute layout parameters. float anchorXStart{anchorX}; @@ -192,22 +192,19 @@ void FlexboxComponent::computeLayout() } } - // Apply right-align + // Apply right-align to the images on the last row, if needed. if (mAlignment == "right") { - unsigned int m = i % std::max(1, static_cast(mItemsPerLine)); - unsigned int n = m > 0 ? mItemsPerLine - m : m; - i = 0; - unsigned int line = 1; + std::vector imagesToAlign; for (auto& image : mImages) { if (!image.second.isVisible()) continue; - if (line == mLines) - image.second.setPosition( - image.second.getPosition().x + - floorf((maxItemSize.x + mItemMargin.x) * static_cast(n)), - image.second.getPosition().y); - if ((i++ + 1) % std::max(1, static_cast(mItemsPerLine)) == 0) - line++; + // Only include images on the last row. + if (image.second.getPosition().y == anchorY) + imagesToAlign.push_back(&image.second); + } + for (auto& moveImage : imagesToAlign) { + float offset = (maxItemSize.x + mItemMargin.x) * (grid.x - imagesToAlign.size()); + moveImage->setPosition(moveImage->getPosition().x + offset, moveImage->getPosition().y); } } From 975ff0eb695ce2f86300f3cfbdfe21299b56c1f0 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 17 Oct 2021 21:20:17 +0200 Subject: [PATCH 076/128] Fixed a potential rounding issue. --- es-core/src/components/MenuComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/components/MenuComponent.cpp b/es-core/src/components/MenuComponent.cpp index e7c3c0964..f1cfe029d 100644 --- a/es-core/src/components/MenuComponent.cpp +++ b/es-core/src/components/MenuComponent.cpp @@ -103,7 +103,7 @@ void MenuComponent::updateSize() int i = 0; while (i < mList->size()) { // Add the separator height to the row height so that it also gets properly rendered. - float rowHeight = mList->getRowHeight(i) + (1 * Renderer::getScreenHeightModifier()); + float rowHeight = mList->getRowHeight(i) + (1.0f * Renderer::getScreenHeightModifier()); if (height + rowHeight < maxHeight) height += rowHeight; else From 11665394669cf14915ce30a0d320d94cfe722e89 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 17 Oct 2021 21:51:21 +0200 Subject: [PATCH 077/128] Fixed a general ImageComponent scaling issue caused by incorrect rounding. --- es-core/src/components/ImageComponent.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index f6c2d2af2..0743005a0 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -130,9 +130,9 @@ void ImageComponent::resize() } } - mSize.x = floorf(mSize.x); - mSize.y = floorf(mSize.y); - // mSize.y() should already be rounded. + mSize.x = ceilf(mSize.x); + mSize.y = ceilf(mSize.y); + mTexture->rasterizeAt(static_cast(mSize.x), static_cast(mSize.y)); onSizeChanged(); From 94c825e3a37c03a3a927ad2f7968d2c693315ac3 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 18 Oct 2021 18:07:20 +0200 Subject: [PATCH 078/128] Changed the 'marquee' variable names to 'loop' in TextListComponent.h --- es-core/src/components/TextListComponent.h | 66 +++++++++++----------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index 35174aed0..1c60e8ddd 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -24,7 +24,7 @@ struct TextListData { std::shared_ptr textCache; }; -// A graphical list. Supports multiple colors for rows and scrolling. +// A scrollable text list supporting multiple row colors. template class TextListComponent : public IList { protected: @@ -108,10 +108,10 @@ protected: virtual void onCursorChanged(const CursorState& state) override; private: - int mMarqueeOffset; - int mMarqueeOffset2; - int mMarqueeTime; - bool mMarqueeScroll; + int mLoopOffset; + int mLoopOffset2; + int mLoopTime; + bool mLoopScroll; Alignment mAlignment; float mHorizontalMargin; @@ -138,10 +138,10 @@ TextListComponent::TextListComponent(Window* window) : IList(window) , mSelectorImage(window) { - mMarqueeOffset = 0; - mMarqueeOffset2 = 0; - mMarqueeTime = 0; - mMarqueeScroll = false; + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; + mLoopScroll = false; mHorizontalMargin = 0.0f; mAlignment = ALIGN_CENTER; @@ -271,26 +271,26 @@ template void TextListComponent::render(const glm::mat4& parentT // Render text. glm::mat4 drawTrans{trans}; - // Currently selected item text might be scrolling. - if (mCursor == i && mMarqueeOffset > 0) + // Currently selected item text might be looping. + if (mCursor == i && mLoopOffset > 0) drawTrans = glm::translate( - drawTrans, offset - glm::vec3{static_cast(mMarqueeOffset), 0.0f, 0.0f}); + drawTrans, offset - glm::vec3{static_cast(mLoopOffset), 0.0f, 0.0f}); else drawTrans = glm::translate(drawTrans, offset); // Needed to avoid flickering when returning to the start position. - if (mMarqueeOffset == 0 && mMarqueeOffset2 == 0) - mMarqueeScroll = false; + if (mLoopOffset == 0 && mLoopOffset2 == 0) + mLoopScroll = false; Renderer::setMatrix(drawTrans); font->renderTextCache(entry.data.textCache.get()); - // Render currently selected row again if marquee is scrolled far enough for it to repeat. - if ((mCursor == i && mMarqueeOffset2 < 0) || (mCursor == i && mMarqueeScroll)) { - mMarqueeScroll = true; + // Render currently selected row again if text is moved far enough for it to repeat. + if ((mCursor == i && mLoopOffset2 < 0) || (mCursor == i && mLoopScroll)) { + mLoopScroll = true; drawTrans = trans; drawTrans = glm::translate( - drawTrans, offset - glm::vec3{static_cast(mMarqueeOffset2), 0.0f, 0.0f}); + drawTrans, offset - glm::vec3{static_cast(mLoopOffset2), 0.0f, 0.0f}); Renderer::setMatrix(drawTrans); font->renderTextCache(entry.data.textCache.get()); } @@ -353,11 +353,11 @@ template void TextListComponent::update(int deltaTime) stopScrolling(); if (!isScrolling() && size() > 0) { - // Always reset the marquee offsets. - mMarqueeOffset = 0; - mMarqueeOffset2 = 0; + // Always reset the loop offsets. + mLoopOffset = 0; + mLoopOffset2 = 0; - // If we're not scrolling and this object's text exceeds our size, then marquee it. + // If we're not scrolling and this object's text exceeds our size, then loop it. const float textLength = mFont ->sizeText(Utils::String::toUpper( mEntries.at(static_cast(mCursor)).name)) @@ -374,16 +374,16 @@ template void TextListComponent::update(int deltaTime) const float returnTime = (returnLength * 1000.0f) / speed; const int maxTime = static_cast(delay + scrollTime + returnTime); - mMarqueeTime += deltaTime; - while (mMarqueeTime > maxTime) - mMarqueeTime -= maxTime; + mLoopTime += deltaTime; + while (mLoopTime > maxTime) + mLoopTime -= maxTime; - mMarqueeOffset = static_cast(Utils::Math::loop(delay, scrollTime + returnTime, - static_cast(mMarqueeTime), - scrollLength + returnLength)); + mLoopOffset = static_cast(Utils::Math::loop(delay, scrollTime + returnTime, + static_cast(mLoopTime), + scrollLength + returnLength)); - if (mMarqueeOffset > (scrollLength - (limit - returnLength))) - mMarqueeOffset2 = static_cast(mMarqueeOffset - (scrollLength + returnLength)); + if (mLoopOffset > (scrollLength - (limit - returnLength))) + mLoopOffset2 = static_cast(mLoopOffset - (scrollLength + returnLength)); } } @@ -405,9 +405,9 @@ void TextListComponent::add(const std::string& name, const T& obj, unsigned i template void TextListComponent::onCursorChanged(const CursorState& state) { - mMarqueeOffset = 0; - mMarqueeOffset2 = 0; - mMarqueeTime = 0; + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; if (mCursorChangedCallback) mCursorChangedCallback(state); From 1650b33b9ae3208112df70a5353a54070e30bd5c Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 18 Oct 2021 18:12:19 +0200 Subject: [PATCH 079/128] Changed a few code comments. --- es-app/src/guis/GuiMenu.cpp | 6 +++--- es-app/src/views/gamelist/GridGameListView.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index f339f06ee..ffa9cd0f2 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -61,7 +61,7 @@ GuiMenu::GuiMenu(Window* window) if (isFullUI) addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherOptions(); }); - // TEMPORARY - disabled for now, will be used in the future. + // TEMPORARY: Disabled for now, will be used in the future. // if (isFullUI) // addEntry("UTILITIES", 0x777777FF, true, [this] { // openUtilitiesMenu(); }); @@ -600,8 +600,8 @@ void GuiMenu::openSoundOptions() { auto s = new GuiSettings(mWindow, "SOUND SETTINGS"); -// TEMPORARY - Hide the volume slider on macOS and BSD Unix until the volume control logic -// has been implemented for these operating systems. +// TODO: Hide the volume slider on macOS and BSD Unix until the volume control logic has been +// implemented for these operating systems. #if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) // System volume. auto system_volume = std::make_shared(mWindow, 0.f, 100.f, 1.f, "%"); diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index 1efa09469..284762deb 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -493,7 +493,7 @@ void GridGameListView::updateInfoPanel() if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) || (!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) { - // TEMPORARY - This does not seem to work, needs to be reviewed later. + // TODO: This does not seem to work, needs to be reviewed later. // auto func = [comp](float t) { auto func = [](float t) { // comp->setOpacity(static_cast(glm::mix(0.0f, 1.0f, t) * 255)); From c3c9e8408c2ff1c5df51c6544200b0b00b5c0623 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 18 Oct 2021 18:13:21 +0200 Subject: [PATCH 080/128] Adjusted the colors for the alt and shift keys on the virtual keyboard. --- es-core/src/guis/GuiTextEditKeyboardPopup.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp index a3ce28161..8a1c270f3 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp @@ -567,8 +567,8 @@ void GuiTextEditKeyboardPopup::shiftKeys() mShift = !mShift; if (mShift) { - mShiftButton->setFlatColorFocused(0xFF2222FF); - mShiftButton->setFlatColorUnfocused(0xFF2222FF); + mShiftButton->setFlatColorFocused(0xF26767FF); + mShiftButton->setFlatColorUnfocused(0xF26767FF); } else { mShiftButton->setFlatColorFocused(0x878787FF); @@ -600,8 +600,8 @@ void GuiTextEditKeyboardPopup::altKeys() mAlt = !mAlt; if (mAlt) { - mAltButton->setFlatColorFocused(0xFF2222FF); - mAltButton->setFlatColorUnfocused(0xFF2222FF); + mAltButton->setFlatColorFocused(0xF26767FF); + mAltButton->setFlatColorUnfocused(0xF26767FF); } else { mAltButton->setFlatColorFocused(0x878787FF); From 0fecb43066f3fcc5245e8b82aafee5cde6e7dbdc Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 18 Oct 2021 18:14:49 +0200 Subject: [PATCH 081/128] Increased the maximum supported display height to 7680 pixels. --- es-app/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index be86c130f..c68c06bbb 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -232,7 +232,7 @@ bool parseArgs(int argc, char* argv[]) } int width = atoi(argv[i + 1]); int height = atoi(argv[i + 2]); - if (width < 224 || height < 224 || width > 7680 || height > 4320 || + if (width < 224 || height < 224 || width > 7680 || height > 7680 || height < width / 4 || width < height / 2) { std::cerr << "Error: Unsupported resolution " << width << "x" << height << " supplied.\n"; From ccc3cae46b721d6c4c77b1f1e38256f80d786532 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 18 Oct 2021 19:15:50 +0200 Subject: [PATCH 082/128] Fixed some small rounding issues in TextListComponent.h --- es-core/src/components/TextListComponent.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index 1c60e8ddd..c551ec9b8 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -223,8 +223,10 @@ template void TextListComponent::render(const glm::mat4& parentT dim.y = (trans[1].y * dim.y + trans[3].y) - trans[3].y; Renderer::pushClipRect( - glm::ivec2{static_cast(trans[3].x + mHorizontalMargin), static_cast(trans[3].y)}, - glm::ivec2{static_cast(dim.x - mHorizontalMargin * 2.0f), static_cast(dim.y)}); + glm::ivec2{static_cast(std::round(trans[3].x + mHorizontalMargin)), + static_cast(std::round(trans[3].y))}, + glm::ivec2{static_cast(std::round(dim.x - mHorizontalMargin * 2.0f)), + static_cast(std::round(dim.y))}); for (int i = startEntry; i < listCutoff; i++) { typename IList::Entry& entry = mEntries.at(static_cast(i)); From 484606fb6f72442ef8653bc60ff6c0b54d91d729 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 18 Oct 2021 19:24:47 +0200 Subject: [PATCH 083/128] Added horizontal scrolling of long game names to the scraper GUI. --- es-app/src/guis/GuiScraperSearch.cpp | 9 ++- es-core/src/components/ComponentList.cpp | 88 +++++++++++++++++++++--- es-core/src/components/ComponentList.h | 15 ++++ 3 files changed, 102 insertions(+), 10 deletions(-) diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 14da96239..bf7aba7b9 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -327,6 +327,7 @@ void GuiScraperSearch::search(const ScraperSearchParams& params) mScrapeResult = {}; mResultList->clear(); + mResultList->setLoopRows(false); mScraperResults.clear(); mMDRetrieveURLsHandle.reset(); mThumbnailReqMap.clear(); @@ -355,6 +356,7 @@ void GuiScraperSearch::onSearchDone(const std::vector& resu mResultList->clear(); mScraperResults = results; + mResultList->setLoopRows(true); auto font = Font::get(FONT_SIZE_MEDIUM); unsigned int color = 0x777777FF; @@ -389,7 +391,7 @@ void GuiScraperSearch::onSearchDone(const std::vector& resu row.addElement( std::make_shared( mWindow, Utils::String::toUpper(results.at(i).mdl.get("name")), font, color), - true); + false); row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); }); mResultList->addRow(row); } @@ -562,8 +564,10 @@ bool GuiScraperSearch::input(InputConfig* config, Input input) else if (mSearchType == ACCEPT_SINGLE_MATCHES && !mFoundGame) allowRefine = true; - if (allowRefine) + if (allowRefine) { + mResultList->stopLooping(); openInputScreen(mLastSearch); + } } // If multi-scraping, skip game unless the result has already been accepted. @@ -589,6 +593,7 @@ void GuiScraperSearch::render(const glm::mat4& parentTrans) void GuiScraperSearch::returnResult(ScraperSearchResult result) { + mResultList->setLoopRows(false); mBlockAccept = true; mAcceptedResult = true; diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index 31eba491a..849e90195 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -8,6 +8,8 @@ #include "components/ComponentList.h" +#include "resources/Font.h" + #define TOTAL_HORIZONTAL_PADDING_PX 20.0f ComponentList::ComponentList(Window* window) @@ -18,6 +20,11 @@ ComponentList::ComponentList(Window* window) , mSingleRowScroll{false} , mSelectorBarOffset{0.0f} , mCameraOffset{0.0f} + , mLoopRows{false} + , mLoopScroll{false} + , mLoopOffset{0} + , mLoopOffset2{0} + , mLoopTime{0} , mScrollIndicatorStatus{SCROLL_NONE} { // Adjust the padding relative to the aspect ratio and screen resolution to make it look @@ -120,6 +127,12 @@ bool ComponentList::input(InputConfig* config, Input input) void ComponentList::update(int deltaTime) { + if (!mFocused && mLoopRows) { + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; + } + const float totalHeight = getTotalRowHeight(); // Scroll indicator logic, used by ScrollIndicatorComponent. @@ -152,10 +165,39 @@ void ComponentList::update(int deltaTime) listUpdate(deltaTime); if (size()) { + float rowWidth{0.0f}; + // Update our currently selected row. for (auto it = mEntries.at(mCursor).data.elements.cbegin(); - it != mEntries.at(mCursor).data.elements.cend(); it++) + it != mEntries.at(mCursor).data.elements.cend(); it++) { it->component->update(deltaTime); + rowWidth += it->component->getSize().x; + } + + if (mLoopRows && rowWidth + mHorizontalPadding / 2.0f > mSize.x) { + // Loop the text. + const float speed{ + Font::get(FONT_SIZE_MEDIUM)->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x * 0.247f}; + const float delay{1300.0f}; + const float scrollLength{rowWidth}; + const float returnLength{speed * 1.5f}; + const float scrollTime{(scrollLength * 1000.0f) / speed}; + const float returnTime{(returnLength * 1000.0f) / speed}; + const int maxTime{static_cast(delay + scrollTime + returnTime)}; + + mLoopTime += deltaTime; + while (mLoopTime > maxTime) + mLoopTime -= maxTime; + + mLoopOffset = static_cast(Utils::Math::loop(delay, scrollTime + returnTime, + static_cast(mLoopTime), + scrollLength + returnLength)); + + if (mLoopOffset > (scrollLength - (mSize.x - returnLength))) + mLoopOffset2 = static_cast(mLoopOffset - (scrollLength + returnLength)); + else if (mLoopOffset2 < 0) + mLoopOffset2 = 0; + } } } @@ -163,6 +205,12 @@ void ComponentList::onCursorChanged(const CursorState& state) { mSetupCompleted = true; + if (mLoopRows) { + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; + } + // Update the selector bar position. // In the future this might be animated. mSelectorBarOffset = 0; @@ -233,22 +281,47 @@ 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; - Renderer::pushClipRect( - glm::ivec2{static_cast(std::round(trans[3].x)), - static_cast(std::round(trans[3].y))}, - glm::ivec2{static_cast(std::round(dim.x)), static_cast(std::round(dim.y))}); + const int clipRectPosX{static_cast(std::round(trans[3].x))}; + const int clipRectPosY{static_cast(std::round(trans[3].y))}; + const int clipRectSizeX{static_cast(std::round(dim.x))}; + const int clipRectSizeY{static_cast(std::round(dim.y))}; + + Renderer::pushClipRect(glm::ivec2{clipRectPosX, clipRectPosY}, + glm::ivec2{clipRectSizeX, clipRectSizeY}); // Scroll the camera. trans = glm::translate(trans, glm::vec3{0.0f, -mCameraOffset, 0.0f}); + glm::mat4 loopTrans{trans}; + // Draw our entries. std::vector drawAfterCursor; bool drawAll; for (size_t i = 0; i < mEntries.size(); i++) { + + if (mLoopRows && mFocused && mLoopOffset > 0) { + loopTrans = + glm::translate(trans, glm::vec3{static_cast(-mLoopOffset), 0.0f, 0.0f}); + } + auto& entry = mEntries.at(i); drawAll = !mFocused || i != static_cast(mCursor); for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); it++) { if (drawAll || it->invert_when_selected) { + auto renderLoopFunc = [&]() { + // Needed to avoid flickering when returning to the start position. + if (mLoopOffset == 0 && mLoopOffset2 == 0) + mLoopScroll = false; + it->component->render(loopTrans); + // Render row again if text is moved far enough for it to repeat. + if (mLoopOffset2 < 0 || mLoopScroll) { + mLoopScroll = true; + loopTrans = glm::translate( + trans, glm::vec3{static_cast(-mLoopOffset2), 0.0f, 0.0f}); + it->component->render(loopTrans); + } + }; + // For the row where the cursor is at, we want to remove any hue from the // font or image before inverting, as it would otherwise lead to an ugly // inverted color (e.g. red inverting to a green hue). @@ -267,15 +340,14 @@ void ComponentList::render(const glm::mat4& parentTrans) unsigned char byteBlue = origColor >> 8 & 0xFF; // If it's neutral, just proceed with normal rendering. if (byteRed == byteGreen && byteGreen == byteBlue) { - it->component->render(trans); + renderLoopFunc(); } else { if (isTextComponent) it->component->setColor(DEFAULT_INVERTED_TEXTCOLOR); else it->component->setColorShift(DEFAULT_INVERTED_IMAGECOLOR); - - it->component->render(trans); + renderLoopFunc(); // Revert to the original color after rendering. if (isTextComponent) it->component->setColor(origColor); diff --git a/es-core/src/components/ComponentList.h b/es-core/src/components/ComponentList.h index d1a3eee01..8b9d9f76c 100644 --- a/es-core/src/components/ComponentList.h +++ b/es-core/src/components/ComponentList.h @@ -86,6 +86,15 @@ public: float getTotalRowHeight() const; float getRowHeight(int row) const { return getRowHeight(mEntries.at(row).data); } + // Horizontal looping for row content that doesn't fit on-screen. + void setLoopRows(bool state) { mLoopRows = state; } + void stopLooping() + { + mLoopOffset = 0; + mLoopOffset2 = 0; + mLoopTime = 0; + } + void resetScrollIndicatorStatus() { mScrollIndicatorStatus = SCROLL_NONE; @@ -126,6 +135,12 @@ private: float mSelectorBarOffset; float mCameraOffset; + bool mLoopRows; + bool mLoopScroll; + int mLoopOffset; + int mLoopOffset2; + int mLoopTime; + std::function mCursorChangedCallback; std::function mScrollIndicatorChangedCallback; From 96a3b581a22fc4a15a1637a0e078ac01b4cb5275 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 18 Oct 2021 19:46:41 +0200 Subject: [PATCH 084/128] Documentation update. --- CHANGELOG.md | 8 ++++++++ CONTRIBUTING.md | 1 + USERGUIDE-DEV.md | 2 ++ 3 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a83df73..f21da3ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ * Added the ability to make complementary game system customizations without having to replace the entire bundled es_systems.xml file * Added support for an optional \ tag for es_systems.xml that can be used to override the default \ systems sorting * Added menu scroll indicators showing if there are additional entries available below or above what's currently shown on screen +* Improved the layout of the scraper GUIs (single-game scraper and multi-scraper) +* Added horizontal scrolling of long game names to the scraper GUIs * Improved the gamelist filter screen to not allow filtering of values where there is no actual data to filter, e.g. Favorites for a system with no favorite games * Grayed out all fields in the gamelist filter screen where there is no data to filter, previously some fields were removed entirely and some could still be used * Added the ability to filter on blank/unknown values for Genre, Player, Developer, Publisher and Alternative emulator. @@ -25,18 +27,22 @@ * Lowered the minimum supported screen resolution from 640x480 to 224x224 to support arcade cabinet displays such as those running at 384x224 and 224x384 * Expanded the themeable options for "helpsystem" to support custom button graphics, dimmed text and icon colors, upper/lower/camel case and custom spacing * Made the scrolling speed of ScrollableContainer more consistent across various screen resolutions and display aspect ratios +* Decreased the amount of text that ScrollableContainer renders above and below the starting position as content is scrolled * Made the game name and description stop scrolling when running the media viewer, the screensaver or when running in the background while a game is launched * Added notification popups when plugging in or removing controllers * Changed to loading the default theme set rbsimple-DE instead of the first available theme if the currently configured theme is missing * Added support for using the left and right trigger buttons in the help prompts * Removed the "Choose" entry from the help prompts in the gamelist view +* Replaced a number of help prompt hacks with proper solutions * Changed the "Toggle screensaver" help entry in the system view to simply "Screensaver" +* Changed the font size for the custom collection deletion screen to the same size as for all other menus * Added support for upscaling bitmap images using linear filtering * Changed the marquee image upscale filtering from nearest neighbor to linear for the launch screen and the gamelist views * Moved the Media Viewer and Screensaver settings higher in the UI Settings menu * Moved the game media directory setting to the top of the Other Settings menu, following the new Alternative Emulators entry * Added a blinking cursor to TextEditComponent * Changed the filter description "Text filter (game name)" to "Game name" +* Removed a margin hack from TextComponent and if abbreviated strings end with a space character, that space is now removed * Added support for multi-select total count and exclusive multi-select to OptionListComponent * Added support for a maximum name length to OptionListComponent (non-multiselect only) with an abbreviation of the name if it exceeds this value * Added support for key repeat to OptionListComponent, making it possible to cycle through the options by holding the left or right button @@ -70,6 +76,7 @@ * When scraping in interactive mode, the game counter was not decreased when skipping games, making it impossible to skip the final games in the queue * When scraping in interactive mode, "No games found" results could be accepted using the "A" button * When scraping in interactive mode, any refining done using the "Y" button shortcut would not be shown when doing another refine using the "Refine search" button +* Fixed multiple minor rendering issues where graphics would be slightly cut off or incorrectly resized * Under some circumstances ScrollableContainer (used for the game descriptions) would contain a partially rendered bottom line * If the TextListComponent height was not evenly dividable by the font height + line spacing, a partial bottom row would get rendered * The line spacing for TextListComponent was incorrectly calculated for some resolutions such as 2560x1440 @@ -86,6 +93,7 @@ * Under some circumstances and at some screen resolutions, the last menu separator line would not get rendered (still an issue at extreme resolutions like 320x240) * When scrolling in menus, pressing other buttons than "Up" or "Down" did not stop the scrolling which caused all sorts of weird behavior * With the menu scale-up effect enabled and entering a submenu before the parent menu was completely scaled up, the parent would get stuck at a semi-scaled size +* The custom collection deletion screen had incorrect row heights when running at lower resolutions such as 1280x720 * If there was an abbreviated full system name for the "Gamelist on startup" option, that abbreviation would also get displayed when opening the selector window * Really long theme set names would not get abbreviated in the UI settings menu, leading to a garbled "Theme set" setting row * Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a gamelist on startup had been set diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe83c2b6b..b37752bfe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,6 +48,7 @@ This plan is under constant review so expect it to change from time to time. Sti #### v1.4 * Localization/multi-language support +* Reorganize the menus, possibly adding basic/advanced modes * Authoring tools to clean up orphaned gamelist entries, media files etc. * Scrollbar component for the gamelist view which can be used by the themes * Web proxy support for the scraper diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index c0117dc04..6c944394a 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -47,6 +47,8 @@ The following operating systems have been tested (all for the x86 architecture u **Note:** If using a Mac with an ARM CPU (e.g. M1) you need to install the x86 version of RetroArch and any other emulators, or you won't be able to launch any games. This will be fixed whenever a native macOS ARM build of ES-DE is released. +As for display resolutions, the minimum pixel value is 224 and the maximum is 7680. This means that you can run ES-DE at for instance 320x224 all the way up to 7680x4320 (8K UHD). Vertical screen orientation is also supported, as well as ultra-wide resolutions like 3840x1440. Note that there could be some minor visual glitches when running in vertical orientation (this will be fixed in future ES-DE releases) and for the best experience you will probably need to use a customized theme set when running at extreme or unusual resolutions. + The installation procedure is just covered briefly here and may differ a bit for your specific operating system, so in case of problems refer to your system documentation. **Installing a Linux .deb package** From 114c91679e68b5fc8cd0b28b906b48d1a123ec18 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 18 Oct 2021 19:58:04 +0200 Subject: [PATCH 085/128] Increased the start delay slightly for the scraper GUI game name scrolling. --- es-core/src/components/ComponentList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index 849e90195..1457e3d14 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -178,7 +178,7 @@ void ComponentList::update(int deltaTime) // Loop the text. const float speed{ Font::get(FONT_SIZE_MEDIUM)->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x * 0.247f}; - const float delay{1300.0f}; + const float delay{1500.0f}; const float scrollLength{rowWidth}; const float returnLength{speed * 1.5f}; const float scrollTime{(scrollLength * 1000.0f) / speed}; From 022f8c7e8b8efff3e079cd543bc4f95ab4ac21d6 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 15:30:35 +0200 Subject: [PATCH 086/128] Fixed an issue where resizing in SwitchComponent would not reposition the image. --- es-core/src/components/SwitchComponent.cpp | 10 ++++++++++ es-core/src/components/SwitchComponent.h | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/es-core/src/components/SwitchComponent.cpp b/es-core/src/components/SwitchComponent.cpp index 23f64dd24..a2712ec5d 100644 --- a/es-core/src/components/SwitchComponent.cpp +++ b/es-core/src/components/SwitchComponent.cpp @@ -49,6 +49,16 @@ void SwitchComponent::render(const glm::mat4& parentTrans) renderChildren(trans); } +void SwitchComponent::setResize(float width, float height) +{ + // Reposition the switch after resizing to make it centered. + const glm::vec2 oldSize = mImage.getSize(); + mImage.setResize(width, height); + const float xDiff = oldSize.x - mImage.getSize().x; + const float yDiff = oldSize.y - mImage.getSize().y; + mImage.setPosition(mImage.getPosition().x + xDiff, mImage.getPosition().y + yDiff / 2.0f); +} + void SwitchComponent::setState(bool state) { mState = state; diff --git a/es-core/src/components/SwitchComponent.h b/es-core/src/components/SwitchComponent.h index c21219144..a0d1ac33c 100644 --- a/es-core/src/components/SwitchComponent.h +++ b/es-core/src/components/SwitchComponent.h @@ -22,7 +22,7 @@ public: void render(const glm::mat4& parentTrans) override; void onSizeChanged() override { mImage.setSize(mSize); } - void setResize(float width, float height) override { mImage.setResize(width, height); } + void setResize(float width, float height) override; bool getState() const { return mState; } void setState(bool state); From bd62f2af14cf20a79f077e6ab7f9bf73ab031eaa Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 15:36:16 +0200 Subject: [PATCH 087/128] Fixed an issue where the bar and knob in SliderComponent were not correctly aligned vertically. --- es-core/src/components/SliderComponent.cpp | 52 ++++++++++++++-------- es-core/src/components/SliderComponent.h | 1 + 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/es-core/src/components/SliderComponent.cpp b/es-core/src/components/SliderComponent.cpp index bd0c40696..07eb7d36c 100644 --- a/es-core/src/components/SliderComponent.cpp +++ b/es-core/src/components/SliderComponent.cpp @@ -15,15 +15,16 @@ SliderComponent::SliderComponent( Window* window, float min, float max, float increment, const std::string& suffix) - : GuiComponent(window) - , mMin(min) - , mMax(max) - , mSingleIncrement(increment) - , mMoveRate(0) - , mKnob(window) - , mSuffix(suffix) + : GuiComponent{window} + , mMin{min} + , mMax{max} + , mSingleIncrement{increment} + , mMoveRate{0.0f} + , mBarHeight{0.0f} + , mKnob{window} + , mSuffix{suffix} { - assert((min - max) != 0); + assert((min - max) != 0.0f); // Some sane default value. mValue = (max + min) / 2.0f; @@ -41,7 +42,7 @@ bool SliderComponent::input(InputConfig* config, Input input) if (input.value) setValue(mValue - mSingleIncrement); - mMoveRate = input.value ? -mSingleIncrement : 0; + mMoveRate = input.value ? -mSingleIncrement : 0.0f; mMoveAccumulator = -MOVE_REPEAT_DELAY; return true; } @@ -49,13 +50,13 @@ bool SliderComponent::input(InputConfig* config, Input input) if (input.value) setValue(mValue + mSingleIncrement); - mMoveRate = input.value ? mSingleIncrement : 0; + mMoveRate = input.value ? mSingleIncrement : 0.0f; mMoveAccumulator = -MOVE_REPEAT_DELAY; return true; } } else { - mMoveRate = 0; + mMoveRate = 0.0f; } return GuiComponent::input(config, input); @@ -88,10 +89,9 @@ void SliderComponent::render(const glm::mat4& parentTrans) mValueCache->metrics.size.x + (4.0f * Renderer::getScreenWidthModifier()) : 0.0f)}; - // Render line. - const float lineWidth{2.0f * Renderer::getScreenHeightModifier()}; - Renderer::drawRect(mKnob.getSize().x / 2.0f, mSize.y / 2.0f - lineWidth / 2.0f, width, - lineWidth, 0x777777FF, 0x777777FF); + // Render bar. + Renderer::drawRect(mKnob.getSize().x / 2.0f, mSize.y / 2.0f - mBarHeight / 2.0f, width, + mBarHeight, 0x777777FF, 0x777777FF); // Render knob. mKnob.render(trans); @@ -143,14 +143,28 @@ void SliderComponent::onValueChanged() mValueCache->metrics.size.x = textSize.x; // Fudge the width. } - // Update knob position/size. - mKnob.setResize(0, mSize.y * 0.7f); - float lineLength = + mKnob.setResize(0.0f, std::round(mSize.y * 0.7f)); + + float barLength = mSize.x - mKnob.getSize().x - (mValueCache ? mValueCache->metrics.size.x + (4.0f * Renderer::getScreenWidthModifier()) : 0.0f); - mKnob.setPosition(((mValue - mMin / 2.0f) / mMax) * lineLength + mKnob.getSize().x / 2.0f, + int barHeight = static_cast(std::round(2.0f * Renderer::getScreenHeightModifier())); + + // For very low resolutions, make sure the bar height is not rounded to zero. + if (barHeight == 0) + 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) + mKnob.setResize(mKnob.getSize().x - 1.0f, mKnob.getSize().y - 1.0f); + else if (barHeight == 1 && static_cast(mKnob.getSize().y) % 2 == 0) + mKnob.setResize(mKnob.getSize().x - 1.0f, mKnob.getSize().y - 1); + + mBarHeight = static_cast(barHeight); + + mKnob.setPosition(((mValue - mMin / 2.0f) / mMax) * barLength + mKnob.getSize().x / 2.0f, mSize.y / 2.0f); } diff --git a/es-core/src/components/SliderComponent.h b/es-core/src/components/SliderComponent.h index 1e6214bc8..9392364d9 100644 --- a/es-core/src/components/SliderComponent.h +++ b/es-core/src/components/SliderComponent.h @@ -45,6 +45,7 @@ private: float mValue; float mSingleIncrement; float mMoveRate; + float mBarHeight; int mMoveAccumulator; ImageComponent mKnob; From 39acfdfe00335cff4b68f9c31d53f593fe06df90 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 15:38:13 +0200 Subject: [PATCH 088/128] (rbsimple-DE) Fixed an issue where the 'megadrive' logo was cut off slightly on right side. --- themes/rbsimple-DE/megadrive/images/logo.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/themes/rbsimple-DE/megadrive/images/logo.svg b/themes/rbsimple-DE/megadrive/images/logo.svg index 4bcf3bded..9371aa60a 100644 --- a/themes/rbsimple-DE/megadrive/images/logo.svg +++ b/themes/rbsimple-DE/megadrive/images/logo.svg @@ -3,8 +3,8 @@ + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="567.500px" + height="90.32px" viewBox="0 0 567.500 90.32" enable-background="new 0 0 566.932 90.32" xml:space="preserve"> From 6cee6d2732ea2ff7bae336c5555dc444bc888883 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 15:45:44 +0200 Subject: [PATCH 089/128] Fixed multiple image scaling and rasterization issues. --- es-core/src/components/ImageComponent.cpp | 32 +++++++++-------------- es-core/src/resources/TextureData.cpp | 9 +++---- es-core/src/resources/TextureData.h | 4 +-- es-core/src/resources/TextureResource.cpp | 2 +- es-core/src/resources/TextureResource.h | 2 +- 5 files changed, 21 insertions(+), 28 deletions(-) diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 0743005a0..6da5f3d1a 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -63,11 +63,8 @@ void ImageComponent::resize() else { // SVG rasterization is determined by height and rasterization is done in terms of pixels. // If rounding is off enough in the rasterization step (for images with extreme aspect - // ratios), it can cause cutoff when the aspect ratio breaks. - // So we always make sure the resultant height is an integer to make sure cutoff doesn't - // happen, and scale width from that (you'll see this scattered throughout the function). - // It's important to use floorf rather than round for this, as we never want to round up - // since that can lead to the cutoff just described. + // ratios), it can cause cutoff when the aspect ratio breaks. So we always make sure to + // round accordingly to avoid such issues. if (mTargetIsMax) { mSize = textureSize; @@ -77,13 +74,11 @@ void ImageComponent::resize() // This will be mTargetSize.x. We can't exceed it, nor be lower than it. mSize.x *= resizeScale.x; // We need to make sure we're not creating an image larger than max size. - mSize.y = std::min(floorf(mSize.y * resizeScale.x), mTargetSize.y); + mSize.y = floorf(std::min(mSize.y * resizeScale.x, mTargetSize.y)); } else { // This will be mTargetSize.y(). We can't exceed it. - mSize.y = floorf(mSize.y * resizeScale.y); - // For SVG rasterization, always calculate width from rounded height (see comment - // above). We need to make sure we're not creating an image larger than max size. + mSize.y *= resizeScale.y; mSize.x = std::min((mSize.y / textureSize.y) * textureSize.x, mTargetSize.x); } } @@ -106,9 +101,7 @@ void ImageComponent::resize() float cropPercent = (mSize.x - mTargetSize.x) / (mSize.x * 2.0f); crop(cropPercent, 0.0f, cropPercent, 0.0f); } - // For SVG rasterization, always calculate width from rounded height (see comment - // above). We need to make sure we're not creating an image smaller than min size. - mSize.y = std::max(floorf(mSize.y), mTargetSize.y); + mSize.y = std::max(mSize.y, mTargetSize.y); mSize.x = std::max((mSize.y / textureSize.y) * textureSize.x, mTargetSize.x); } else { @@ -117,23 +110,24 @@ void ImageComponent::resize() mSize = mTargetSize == glm::vec2{} ? textureSize : mTargetSize; // If only one component is set, we resize in a way that maintains aspect ratio. - // For SVG rasterization, we always calculate width from rounded height (see - // comment above). if (!mTargetSize.x && mTargetSize.y) { - mSize.y = floorf(mTargetSize.y); + mSize.y = mTargetSize.y; mSize.x = (mSize.y / textureSize.y) * textureSize.x; } else if (mTargetSize.x && !mTargetSize.y) { - mSize.y = floorf((mTargetSize.x / textureSize.x) * textureSize.y); + mSize.y = (mTargetSize.x / textureSize.x) * textureSize.y; mSize.x = (mSize.y / textureSize.y) * textureSize.x; } } } - mSize.x = ceilf(mSize.x); - mSize.y = ceilf(mSize.y); + // Make sure sub-pixel values are not rounded to zero. + if (mSize.x < 1.0f) + mSize.x = ceilf(mSize.x); + if (mSize.y < 1.0f) + mSize.y = ceilf(mSize.y); - mTexture->rasterizeAt(static_cast(mSize.x), static_cast(mSize.y)); + mTexture->rasterizeAt(mSize.x, mSize.y); onSizeChanged(); } diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp index 70375c5b3..8e1eba5c5 100644 --- a/es-core/src/resources/TextureData.cpp +++ b/es-core/src/resources/TextureData.cpp @@ -73,8 +73,8 @@ bool TextureData::initSVGFromMemory(const std::string& fileData) mSourceHeight = svgImage->height; } - mWidth = static_cast(floorf(floorf(mSourceWidth) * mScaleDuringLoad)); - mHeight = static_cast(floorf(floorf(mSourceHeight) * mScaleDuringLoad)); + mWidth = static_cast(std::round(mSourceWidth * mScaleDuringLoad)); + mHeight = static_cast(std::round(mSourceHeight * mScaleDuringLoad)); if (mWidth == 0) { // Auto scale width to keep aspect ratio. @@ -92,9 +92,8 @@ bool TextureData::initSVGFromMemory(const std::string& fileData) NSVGrasterizer* rast = nsvgCreateRasterizer(); - nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), - static_cast(mWidth), static_cast(mHeight), - static_cast(mWidth) * 4); + nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), mWidth, + mHeight, mWidth * 4); // This is important in order to avoid memory leaks. nsvgDeleteRasterizer(rast); diff --git a/es-core/src/resources/TextureData.h b/es-core/src/resources/TextureData.h index c50cd19d3..0e76d8159 100644 --- a/es-core/src/resources/TextureData.h +++ b/es-core/src/resources/TextureData.h @@ -72,8 +72,8 @@ private: std::string mPath; unsigned int mTextureID; std::vector mDataRGBA; - size_t mWidth; - size_t mHeight; + int mWidth; + int mHeight; float mSourceWidth; float mSourceHeight; float mScaleDuringLoad; diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index 74e5d5bfe..b6f7b8d8a 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -194,7 +194,7 @@ std::shared_ptr TextureResource::get(const std::string& path, return tex; } -void TextureResource::rasterizeAt(size_t width, size_t height) +void TextureResource::rasterizeAt(float width, float height) { if (mTextureData != nullptr) { glm::vec2 textureSize = mTextureData.get()->getSize(); diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 223d60fcc..91ba5ef7e 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -44,7 +44,7 @@ public: // It does unload and re-rasterize the texture though which may cause flickering in some // situations. An alternative is to set a scaling factor directly when loading the texture // using get(), by using the scaleDuringLoad parameter (which also works for raster graphics). - void rasterizeAt(size_t width, size_t height); + void rasterizeAt(float width, float height); glm::vec2 getSourceImageSize() const { return mSourceSize; } virtual ~TextureResource(); From dd94aeec96a2967bb65181bf0fd87f398701e524 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 15:53:31 +0200 Subject: [PATCH 090/128] Fixed two casting issues. --- es-core/src/components/RatingComponent.cpp | 5 ++--- es-core/src/resources/TextureData.cpp | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/es-core/src/components/RatingComponent.cpp b/es-core/src/components/RatingComponent.cpp index 6c2c22c6f..7dcdecf7b 100644 --- a/es-core/src/components/RatingComponent.cpp +++ b/es-core/src/components/RatingComponent.cpp @@ -102,11 +102,10 @@ void RatingComponent::onSizeChanged() mSize.x = mSize.y * NUM_RATING_STARS; if (mSize.y > 0.0f) { - size_t heightPx = static_cast(std::round(mSize.y)); if (mFilledTexture) - mFilledTexture->rasterizeAt(heightPx, heightPx); + mFilledTexture->rasterizeAt(mSize.y, mSize.y); if (mUnfilledTexture) - mUnfilledTexture->rasterizeAt(heightPx, heightPx); + mUnfilledTexture->rasterizeAt(mSize.y, mSize.y); } updateVertices(); diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp index 8e1eba5c5..9537b25d7 100644 --- a/es-core/src/resources/TextureData.cpp +++ b/es-core/src/resources/TextureData.cpp @@ -147,8 +147,8 @@ bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size mDataRGBA.reserve(width * height * 4); mDataRGBA.insert(mDataRGBA.begin(), dataRGBA, dataRGBA + (width * height * 4)); - mWidth = width; - mHeight = height; + mWidth = static_cast(width); + mHeight = static_cast(height); return true; } From 3c567c07d8c13172b34419e2f4c051d91e436c00 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 15:56:05 +0200 Subject: [PATCH 091/128] Some very minor adjustments to the bundled badge graphics files. --- resources/graphics/badge_altemulator.svg | 13 +++++++------ resources/graphics/badge_broken.svg | 11 ++++++----- resources/graphics/badge_completed.svg | 13 +++++++------ resources/graphics/badge_favorite.svg | 13 +++++++------ resources/graphics/badge_kidgame.svg | 13 +++++++------ 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/resources/graphics/badge_altemulator.svg b/resources/graphics/badge_altemulator.svg index 055f15766..f8a9d5442 100644 --- a/resources/graphics/badge_altemulator.svg +++ b/resources/graphics/badge_altemulator.svg @@ -23,11 +23,11 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="19.3509" - inkscape:cx="5.374237" - inkscape:cy="26.957777" + inkscape:zoom="12.066296" + inkscape:cx="11.393424" + inkscape:cy="66.081732" inkscape:document-units="mm" - inkscape:current-layer="layer2" + inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="3840" inkscape:window-height="2065" @@ -175,14 +175,15 @@ + transform="matrix(0.10079384,0,0,0.0816046,-2.2547839e-4,271.5469)" + style="fill:#000000;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#000000;fill-opacity:1" /> + transform="matrix(0.10079384,0,0,0.0816046,-2.2531965e-4,271.54688)" + style="fill:#000000;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#000000;fill-opacity:1" /> image/svg+xml - + @@ -74,14 +74,15 @@ + transform="matrix(0.10079384,0,0,0.0816046,-4.2317622e-4,0.13809161)" + style="fill:#0e0e0e;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#0e0e0e;fill-opacity:1" /> image/svg+xml - + @@ -71,14 +71,15 @@ + transform="matrix(0.10079384,0,0,0.0816046,-4.2392733e-4,0.13809204)" + style="fill:#000000;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#000000;fill-opacity:1" /> image/svg+xml - + @@ -74,14 +74,15 @@ + transform="matrix(0.10079384,0,0,0.0816046,-4.237873e-4,0.13809261)" + style="fill:#000000;fill-opacity:1"> + style="stroke-width:0.26458001;fill:#000000;fill-opacity:1" /> Date: Sat, 23 Oct 2021 17:35:23 +0200 Subject: [PATCH 092/128] Small simplification in ImageComponent. --- es-core/src/components/ImageComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 6da5f3d1a..4055c9989 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -123,9 +123,9 @@ void ImageComponent::resize() // Make sure sub-pixel values are not rounded to zero. if (mSize.x < 1.0f) - mSize.x = ceilf(mSize.x); + mSize.x = 1.0f; if (mSize.y < 1.0f) - mSize.y = ceilf(mSize.y); + mSize.y = 1.0f; mTexture->rasterizeAt(mSize.x, mSize.y); From edc11e625541f337c4a376759c73fb28c32a2635 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 17:36:13 +0200 Subject: [PATCH 093/128] Added a setter to ImageComponent for mForceLoad. --- es-core/src/components/ImageComponent.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 159d65e3b..40b15cf3d 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -30,6 +30,8 @@ public: // Use an already existing texture. void setImage(const std::shared_ptr& texture, bool resizeTexture = true); + void setForceLoad(bool status) { mForceLoad = status; } + void onSizeChanged() override { updateVertices(); } // Resize the image to fit this size. If one axis is zero, scale that axis to maintain From 84514f67d0865c0f43996ef07d47ce311794238d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 19:08:32 +0200 Subject: [PATCH 094/128] Added support for controller types. --- es-app/src/MetaData.cpp | 2 + es-app/src/MetaData.h | 1 + es-app/src/guis/GuiMetaDataEd.cpp | 122 +++++++- es-app/src/guis/GuiMetaDataEd.h | 2 + .../views/gamelist/DetailedGameListView.cpp | 18 +- .../src/views/gamelist/VideoGameListView.cpp | 18 +- es-core/src/ThemeData.cpp | 26 +- es-core/src/components/BadgesComponent.cpp | 190 +++++++++--- es-core/src/components/BadgesComponent.h | 27 +- es-core/src/components/FlexboxComponent.cpp | 286 ++++++++++-------- es-core/src/components/FlexboxComponent.h | 31 +- resources/graphics/badge_controller.svg | 147 +++++++++ .../graphics/controllers/gamepad_generic.svg | 69 +++++ .../controllers/gamepad_nintendo_64.svg | 69 +++++ .../controllers/gamepad_nintendo_nes.svg | 69 +++++ .../controllers/gamepad_nintendo_snes.svg | 69 +++++ .../controllers/gamepad_playstation.svg | 69 +++++ .../graphics/controllers/gamepad_xbox.svg | 69 +++++ .../joycon_left_or_right_nintendo.svg | 69 +++++ .../controllers/joycon_pair_nintendo.svg | 69 +++++ .../controllers/joystick_arcade_2_buttons.svg | 69 +++++ .../controllers/joystick_arcade_3_buttons.svg | 69 +++++ .../controllers/joystick_arcade_4_buttons.svg | 69 +++++ .../controllers/joystick_arcade_6_buttons.svg | 69 +++++ .../graphics/controllers/joystick_generic.svg | 69 +++++ .../graphics/controllers/keyboard_generic.svg | 69 +++++ .../controllers/keyboard_mouse_generic.svg | 69 +++++ .../graphics/controllers/lightgun_generic.svg | 69 +++++ .../controllers/lightgun_nintendo.svg | 69 +++++ .../graphics/controllers/mouse_amiga.svg | 69 +++++ .../graphics/controllers/mouse_generic.svg | 69 +++++ .../controllers/steering_wheel_generic.svg | 69 +++++ .../controllers/trackball_generic.svg | 69 +++++ resources/graphics/controllers/unknown.svg | 69 +++++ .../controllers/wii_remote_nintendo.svg | 69 +++++ .../wii_remote_nunchuck_nintendo.svg | 69 +++++ themes/rbsimple-DE/theme.xml | 10 +- 37 files changed, 2333 insertions(+), 203 deletions(-) create mode 100644 resources/graphics/badge_controller.svg create mode 100644 resources/graphics/controllers/gamepad_generic.svg create mode 100644 resources/graphics/controllers/gamepad_nintendo_64.svg create mode 100644 resources/graphics/controllers/gamepad_nintendo_nes.svg create mode 100644 resources/graphics/controllers/gamepad_nintendo_snes.svg create mode 100644 resources/graphics/controllers/gamepad_playstation.svg create mode 100644 resources/graphics/controllers/gamepad_xbox.svg create mode 100644 resources/graphics/controllers/joycon_left_or_right_nintendo.svg create mode 100644 resources/graphics/controllers/joycon_pair_nintendo.svg create mode 100644 resources/graphics/controllers/joystick_arcade_2_buttons.svg create mode 100644 resources/graphics/controllers/joystick_arcade_3_buttons.svg create mode 100644 resources/graphics/controllers/joystick_arcade_4_buttons.svg create mode 100644 resources/graphics/controllers/joystick_arcade_6_buttons.svg create mode 100644 resources/graphics/controllers/joystick_generic.svg create mode 100644 resources/graphics/controllers/keyboard_generic.svg create mode 100644 resources/graphics/controllers/keyboard_mouse_generic.svg create mode 100644 resources/graphics/controllers/lightgun_generic.svg create mode 100644 resources/graphics/controllers/lightgun_nintendo.svg create mode 100644 resources/graphics/controllers/mouse_amiga.svg create mode 100644 resources/graphics/controllers/mouse_generic.svg create mode 100644 resources/graphics/controllers/steering_wheel_generic.svg create mode 100644 resources/graphics/controllers/trackball_generic.svg create mode 100644 resources/graphics/controllers/unknown.svg create mode 100644 resources/graphics/controllers/wii_remote_nintendo.svg create mode 100644 resources/graphics/controllers/wii_remote_nunchuck_nintendo.svg diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 98a354e59..84f52233d 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -37,6 +37,7 @@ MetaDataDecl gameDecls[] = { {"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, {"playcount", MD_INT, "0", false, "times played", "enter number of times played", false}, +{"controller", MD_CONTROLLER, "", false, "controller type", "select controller type", false}, {"altemulator", MD_ALT_EMULATOR, "", false, "alternative emulator", "select alternative emulator", false}, {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; @@ -57,6 +58,7 @@ MetaDataDecl folderDecls[] = { {"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, {"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, +{"controller", MD_CONTROLLER, "", false, "controller type", "select controller type", false}, {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; // clang-format on diff --git a/es-app/src/MetaData.h b/es-app/src/MetaData.h index 8f0ac5964..e7867e9e4 100644 --- a/es-app/src/MetaData.h +++ b/es-app/src/MetaData.h @@ -32,6 +32,7 @@ enum MetaDataType { // Specialized types. MD_MULTILINE_STRING, + MD_CONTROLLER, MD_ALT_EMULATOR, MD_PATH, MD_RATING, diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index defc04f04..42d9c52a9 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -53,6 +53,12 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, , mMediaFilesUpdated{false} , mInvalidEmulatorEntry{false} { + mControllerTypes = BadgesComponent::getControllerTypes(); + + // Remove the last "unknown" controller entry. + if (mControllerTypes.size() > 1) + mControllerTypes.pop_back(); + addChild(&mBackground); addChild(&mGrid); @@ -137,8 +143,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, ed = std::make_shared(window); // Make the switches slightly smaller. glm::vec2 switchSize{ed->getSize() * 0.9f}; - ed->setResize(switchSize.x, switchSize.y); - ed->setOrigin(-0.05f, -0.09f); + ed->setResize(ceilf(switchSize.x), switchSize.y); ed->setChangedColor(ICONCOLOR_USERMARKED); row.addElement(ed, false, true); @@ -175,6 +180,89 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, std::placeholders::_2); break; } + case MD_CONTROLLER: { + ed = std::make_shared(window, "", + Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), + 0x777777FF, ALIGN_RIGHT); + row.addElement(ed, true); + + auto spacer = std::make_shared(mWindow); + spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0.0f); + row.addElement(spacer, false); + + auto bracket = std::make_shared(mWindow); + bracket->setImage(":/graphics/arrow.svg"); + bracket->setResize(glm::vec2{0.0f, lbl->getFont()->getLetterHeight()}); + row.addElement(bracket, false); + + const std::string title = iter->displayPrompt; + + // OK callback (apply new value to ed). + auto updateVal = [ed, originalValue](const std::string& newVal) { + ed->setValue(newVal); + if (newVal == BadgesComponent::getDisplayName(originalValue)) + ed->setColor(DEFAULT_TEXTCOLOR); + else + ed->setColor(TEXTCOLOR_USERMARKED); + }; + + row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal, + originalValue] { + GuiSettings* s = new GuiSettings(mWindow, title); + + for (auto controllerType : mControllerTypes) { + std::string selectedLabel = ed->getValue(); + std::string label; + ComponentListRow row; + + std::shared_ptr labelText = std::make_shared( + mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + labelText->setSelectable(true); + labelText->setValue(controllerType.displayName); + + label = controllerType.displayName; + + row.addElement(labelText, true); + + row.makeAcceptInputHandler([s, updateVal, controllerType] { + updateVal(controllerType.displayName); + delete s; + }); + + // Select the row that corresponds to the selected label. + if (selectedLabel == label) + s->addRow(row, true); + else + s->addRow(row, false); + } + + // If a value is set, then display "Clear entry" as the last entry. + if (ed->getValue() != "") { + ComponentListRow row; + std::shared_ptr clearText = std::make_shared( + mWindow, ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF); + clearText->setSelectable(true); + row.addElement(clearText, true); + row.makeAcceptInputHandler([s, ed] { + ed->setValue(""); + delete s; + }); + s->addRow(row, false); + } + + float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); + float maxWidthModifier = glm::clamp(0.64f * aspectValue, 0.42f, 0.92f); + float maxWidth = + static_cast(Renderer::getScreenWidth()) * maxWidthModifier; + + s->setMenuSize(glm::vec2{maxWidth, s->getMenuSize().y}); + s->setMenuPosition( + glm::vec3{(s->getSize().x - maxWidth) / 2.0f, mPosition.y, mPosition.z}); + mWindow->pushGui(s); + }); + break; + } case MD_ALT_EMULATOR: { mInvalidEmulatorEntry = false; @@ -296,11 +384,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, s->addRow(row, false); } - // Adjust the width depending on the aspect ratio of the screen, to make the - // screen look somewhat coherent regardless of screen type. The 1.778 aspect - // ratio value is the 16:9 reference. float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); - float maxWidthModifier = glm::clamp(0.64f * aspectValue, 0.42f, 0.92f); float maxWidth = static_cast(Renderer::getScreenWidth()) * maxWidthModifier; @@ -390,10 +474,19 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, assert(ed); mList->addRow(row); - if (iter->type == MD_ALT_EMULATOR && mInvalidEmulatorEntry == true) + if (iter->type == MD_ALT_EMULATOR && mInvalidEmulatorEntry == true) { ed->setValue(ViewController::EXCLAMATION_CHAR + " " + originalValue); - else + } + else if (iter->type == MD_CONTROLLER && mMetaData->get(iter->key) != "") { + std::string displayName = BadgesComponent::getDisplayName(mMetaData->get(iter->key)); + if (displayName != "unknown") + ed->setValue(displayName); + else + ed->setValue(ViewController::EXCLAMATION_CHAR + " " + mMetaData->get(iter->key)); + } + else { ed->setValue(mMetaData->get(iter->key)); + } mEditors.push_back(ed); } @@ -524,6 +617,13 @@ void GuiMetaDataEd::save() if (mMetaDataDecl.at(i).key == "altemulator" && mInvalidEmulatorEntry == true) continue; + if (mMetaDataDecl.at(i).key == "controller" && mEditors.at(i)->getValue() != "") { + std::string shortName = BadgesComponent::getShortName(mEditors.at(i)->getValue()); + if (shortName != "unknown") + mMetaData->set(mMetaDataDecl.at(i).key, shortName); + continue; + } + if (!showHiddenGames && mMetaDataDecl.at(i).key == "hidden" && mEditors.at(i)->getValue() != mMetaData->get("hidden")) hideGameWhileHidden = true; @@ -668,6 +768,12 @@ void GuiMetaDataEd::close() if (key == "altemulator" && mInvalidEmulatorEntry == true) continue; + if (mMetaDataDecl.at(i).key == "controller" && mEditors.at(i)->getValue() != "") { + std::string shortName = BadgesComponent::getShortName(mEditors.at(i)->getValue()); + if (shortName == "unknown" || mMetaDataValue == shortName) + continue; + } + if (mMetaDataValue != mEditorsValue) { metadataUpdated = true; break; diff --git a/es-app/src/guis/GuiMetaDataEd.h b/es-app/src/guis/GuiMetaDataEd.h index ec1793145..e73714e8d 100644 --- a/es-app/src/guis/GuiMetaDataEd.h +++ b/es-app/src/guis/GuiMetaDataEd.h @@ -13,6 +13,7 @@ #include "GuiComponent.h" #include "MetaData.h" +#include "components/BadgesComponent.h" #include "components/ComponentGrid.h" #include "components/NinePatchComponent.h" #include "components/ScrollIndicatorComponent.h" @@ -60,6 +61,7 @@ private: ScraperSearchParams mScraperParams; + std::vector mControllerTypes; std::vector> mEditors; std::vector mMetaDataDecl; diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index b343e8b3c..a47f65f98 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -104,7 +104,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) // Badges. addChild(&mBadges); - mBadges.setOrigin(0.0f, 0.0f); + mBadges.setOrigin(0.5f, 0.5f); mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); mBadges.setSize(mSize.x * 0.15f, mSize.y * 0.2f); mBadges.setDefaultZIndex(50.0f); @@ -410,15 +410,23 @@ void DetailedGameListView::updateInfoPanel() mPlayers.setValue(file->metadata.get("players")); // Populate the badge slots based on game metadata. - std::vector badgeSlots; + std::vector badgeSlots; for (auto badge : mBadges.getBadgeTypes()) { - if (badge == "altemulator") { + BadgesComponent::BadgeInfo badgeInfo; + badgeInfo.badgeType = badge; + if (badge == "controller") { + if (file->metadata.get("controller").compare("") != 0) { + badgeInfo.controllerType = file->metadata.get("controller"); + badgeSlots.push_back(badgeInfo); + } + } + else if (badge == "altemulator") { if (file->metadata.get(badge).compare("") != 0) - badgeSlots.push_back(badge); + badgeSlots.push_back(badgeInfo); } else { if (file->metadata.get(badge).compare("true") == 0) - badgeSlots.push_back(badge); + badgeSlots.push_back(badgeInfo); } } mBadges.setBadges(badgeSlots); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index c0f1c1bc4..699c253f8 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -121,7 +121,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) // Badges. addChild(&mBadges); - mBadges.setOrigin(0.0f, 0.0f); + mBadges.setOrigin(0.5f, 0.5f); mBadges.setPosition(mSize.x * 0.8f, mSize.y * 0.7f); mBadges.setSize(mSize.x * 0.15f, mSize.y * 0.2f); mBadges.setDefaultZIndex(50.0f); @@ -450,15 +450,23 @@ void VideoGameListView::updateInfoPanel() mPlayers.setValue(file->metadata.get("players")); // Populate the badge slots based on game metadata. - std::vector badgeSlots; + std::vector badgeSlots; for (auto badge : mBadges.getBadgeTypes()) { - if (badge == "altemulator") { + BadgesComponent::BadgeInfo badgeInfo; + badgeInfo.badgeType = badge; + if (badge == "controller") { + if (file->metadata.get("controller").compare("") != 0) { + badgeInfo.controllerType = file->metadata.get("controller"); + badgeSlots.push_back(badgeInfo); + } + } + else if (badge == "altemulator") { if (file->metadata.get(badge).compare("") != 0) - badgeSlots.push_back(badge); + badgeSlots.push_back(badgeInfo); } else { if (file->metadata.get(badge).compare("true") == 0) - badgeSlots.push_back(badge); + badgeSlots.push_back(badgeInfo); } } mBadges.setBadges(badgeSlots); diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 603a0131b..e1e83f432 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -155,10 +155,12 @@ std::map> The {"alignment", STRING}, {"itemsPerRow", FLOAT}, {"rows", FLOAT}, - {"itemPlacement", STRING}, {"itemMargin", NORMALIZED_PAIR}, {"slots", STRING}, + {"controllerPos", NORMALIZED_PAIR}, + {"controllerSize", FLOAT}, {"customBadgeIcon", PATH}, + {"customControllerIcon", PATH}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"sound", {{"path", PATH}}}, @@ -519,21 +521,29 @@ void ThemeData::parseElement(const pugi::xml_node& root, } // Special parsing instruction for recurring options. - // Store as it's attribute to prevent nodes overwriting each other. + // Store as its attribute to prevent nodes overwriting each other. if (strcmp(node.name(), "customButtonIcon") == 0) { - const auto btn = node.attribute("button").as_string(""); - if (strcmp(btn, "") == 0) + const auto button = node.attribute("button").as_string(""); + if (strcmp(button, "") == 0) LOG(LogError) << " element requires the `button` property."; else - element.properties[btn] = path; + element.properties[button] = path; } else if (strcmp(node.name(), "customBadgeIcon") == 0) { - const auto btn = node.attribute("badge").as_string(""); - if (strcmp(btn, "") == 0) + const auto badge = node.attribute("badge").as_string(""); + if (strcmp(badge, "") == 0) LOG(LogError) << " element requires the `badge` property."; else - element.properties[btn] = path; + element.properties[badge] = path; + } + else if (strcmp(node.name(), "customControllerIcon") == 0) { + const auto controller = node.attribute("controller").as_string(""); + if (strcmp(controller, "") == 0) + LOG(LogError) + << " element requires the `controller` property."; + else + element.properties[controller] = path; } else element.properties[node.name()] = path; diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index 00c023b64..b932fddea 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -11,6 +11,7 @@ #define SLOT_COMPLETED "completed" #define SLOT_KIDGAME "kidgame" #define SLOT_BROKEN "broken" +#define SLOT_CONTROLLER "controller" #define SLOT_ALTEMULATOR "altemulator" #include "components/BadgesComponent.h" @@ -19,46 +20,134 @@ #include "ThemeData.h" #include "utils/StringUtil.h" +std::vector BadgesComponent::sControllerTypes; + +// clang-format off + +// The "unknown" controller entry has to be placed last. +ControllerTypes sControllerDefinitions [] = { + // shortName displayName fileName + {"gamepad_generic", "Gamepad (Generic)", ":/graphics/controllers/gamepad_generic.svg"}, + {"gamepad_xbox", "Gamepad (Xbox)", ":/graphics/controllers/gamepad_xbox.svg"}, + {"gamepad_playstation", "Gamepad (PlayStation)", ":/graphics/controllers/gamepad_playstation.svg"}, + {"gamepad_nintendo_nes", "Gamepad (Nintendo NES)", ":/graphics/controllers/gamepad_nintendo_nes.svg"}, + {"gamepad_nintendo_snes", "Gamepad (Nintendo SNES)", ":/graphics/controllers/gamepad_nintendo_snes.svg"}, + {"gamepad_nintendo_64", "Gamepad (Nintendo 64)", ":/graphics/controllers/gamepad_nintendo_64.svg"}, + {"joystick_generic", "Joystick (Generic)", ":/graphics/controllers/joystick_generic.svg"}, + {"joystick_arcade_2_buttons", "Joystick (Arcade 2 Buttons)", ":/graphics/controllers/joystick_arcade_2_buttons.svg"}, + {"joystick_arcade_3_buttons", "Joystick (Arcade 3 Buttons)", ":/graphics/controllers/joystick_arcade_3_buttons.svg"}, + {"joystick_arcade_4_buttons", "Joystick (Arcade 4 Buttons)", ":/graphics/controllers/joystick_arcade_4_buttons.svg"}, + {"joystick_arcade_6_buttons", "Joystick (Arcade 6 Buttons)", ":/graphics/controllers/joystick_arcade_6_buttons.svg"}, + {"trackball_generic", "Trackball (Generic)", ":/graphics/controllers/trackball_generic.svg"}, + {"lightgun_generic", "Lightgun (Generic)", ":/graphics/controllers/lightgun_generic.svg"}, + {"lightgun_nintendo", "Lightgun (Nintendo)", ":/graphics/controllers/lightgun_nintendo.svg"}, + {"keyboard_generic", "Keyboard (Generic)", ":/graphics/controllers/keyboard_generic.svg"}, + {"mouse_generic", "Mouse (Generic)", ":/graphics/controllers/mouse_generic.svg"}, + {"mouse_amiga", "Mouse (Amiga)", ":/graphics/controllers/mouse_amiga.svg"}, + {"keyboard_mouse_generic", "Keyboard and Mouse (Generic)", ":/graphics/controllers/keyboard_mouse_generic.svg"}, + {"steering_wheel_generic", "Steering Wheel (Generic)", ":/graphics/controllers/steering_wheel_generic.svg"}, + {"wii_remote_nintendo", "Wii Remote (Nintendo)", ":/graphics/controllers/wii_remote_nintendo.svg"}, + {"wii_remote_nunchuck_nintendo", "Wii Remote and Nunchuck (Nintendo)", ":/graphics/controllers/wii_remote_nunchuck_nintendo.svg"}, + {"joycon_left_or_right_nintendo", "Joy-Con Left or Right (Nintendo)", ":/graphics/controllers/joycon_left_or_right_nintendo.svg"}, + {"joycon_pair_nintendo", "Joy-Con Pair (Nintendo)", ":/graphics/controllers/joycon_pair_nintendo.svg"}, + {"unknown", "Unknown Controller", ":/graphics/controllers/unknown.svg"} +}; + +// clang-format on + BadgesComponent::BadgesComponent(Window* window) : GuiComponent{window} - , mFlexboxComponent{window, mBadgeImages} - , mBadgeTypes{{SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDGAME, SLOT_BROKEN, SLOT_ALTEMULATOR}} + , mFlexboxItems{} + , mFlexboxComponent{window, mFlexboxItems} + , mBadgeTypes{{SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDGAME, SLOT_BROKEN, SLOT_CONTROLLER, + SLOT_ALTEMULATOR}} { mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.svg"; mBadgeIcons[SLOT_COMPLETED] = ":/graphics/badge_completed.svg"; mBadgeIcons[SLOT_KIDGAME] = ":/graphics/badge_kidgame.svg"; mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.svg"; + mBadgeIcons[SLOT_CONTROLLER] = ":/graphics/badge_controller.svg"; mBadgeIcons[SLOT_ALTEMULATOR] = ":/graphics/badge_altemulator.svg"; } -void BadgesComponent::setBadges(const std::vector& badges) +void BadgesComponent::populateControllerTypes() +{ + sControllerTypes.clear(); + for (auto type : sControllerDefinitions) + sControllerTypes.push_back(type); +} + +void BadgesComponent::setBadges(const std::vector& badges) { std::map prevVisibility; + std::map prevPlayers; + std::map prevController; // Save the visibility status to know whether any badges changed. - for (auto& image : mBadgeImages) { - prevVisibility[image.first] = image.second.isVisible(); - image.second.setVisible(false); + for (auto& item : mFlexboxItems) { + prevVisibility[item.label] = item.visible; + prevController[item.label] = item.overlayImage.getTexture()->getTextureFilePath(); + item.visible = false; } for (auto& badge : badges) { auto it = std::find_if( - mBadgeImages.begin(), mBadgeImages.end(), - [badge](std::pair image) { return image.first == badge; }); + mFlexboxItems.begin(), mFlexboxItems.end(), + [badge](FlexboxComponent::FlexboxItem item) { return item.label == badge.badgeType; }); - if (it != mBadgeImages.cend()) - it->second.setVisible(true); + if (it != mFlexboxItems.end()) { + it->visible = true; + if (badge.controllerType != "" && + badge.controllerType != it->overlayImage.getTexture()->getTextureFilePath()) { + + auto it2 = std::find_if(sControllerTypes.begin(), sControllerTypes.end(), + [badge](ControllerTypes controllerType) { + return controllerType.shortName == badge.controllerType; + }); + + if (it2 != sControllerTypes.cend()) { + it->overlayImage.setImage((*it2).fileName); + } + else if (badge.controllerType != "") + it->overlayImage.setImage(sControllerTypes.back().fileName); + } + } } // Only recalculate the flexbox if any badges changed. - for (auto& image : mBadgeImages) { - if (prevVisibility[image.first] != image.second.isVisible()) { + for (auto& item : mFlexboxItems) { + if (prevVisibility[item.label] != item.visible || + prevController[item.label] != item.label) { mFlexboxComponent.onSizeChanged(); break; } } } +const std::string BadgesComponent::getShortName(const std::string& displayName) +{ + auto it = std::find_if(sControllerTypes.begin(), sControllerTypes.end(), + [displayName](ControllerTypes controllerType) { + return controllerType.displayName == displayName; + }); + if (it != sControllerTypes.end()) + return (*it).shortName; + else + return "unknown"; +} + +const std::string BadgesComponent::getDisplayName(const std::string& shortName) +{ + auto it = std::find_if(sControllerTypes.begin(), sControllerTypes.end(), + [shortName](ControllerTypes controllerType) { + return controllerType.shortName == shortName; + }); + if (it != sControllerTypes.end()) + return (*it).displayName; + else + return "unknown"; +} + void BadgesComponent::render(const glm::mat4& parentTrans) { if (!isVisible()) @@ -79,6 +168,8 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, const std::string& element, unsigned int properties) { + populateControllerTypes(); + using namespace ThemeFlags; const ThemeData::ThemeElement* elem{theme->getElement(view, element, "badges")}; @@ -119,27 +210,11 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, } } - if (elem->has("itemPlacement")) { - std::string itemPlacement{elem->get("itemPlacement")}; - if (itemPlacement != "top" && itemPlacement != "center" && itemPlacement != "bottom" && - itemPlacement != "stretch") { - LOG(LogWarning) - << "BadgesComponent: Invalid theme configuration, set to \"" - << itemPlacement << "\""; - } - else { - if (itemPlacement == "top") - itemPlacement = "start"; - else if (itemPlacement == "bottom") - itemPlacement = "end"; - mFlexboxComponent.setItemPlacement(itemPlacement); - } - } - if (elem->has("itemMargin")) { - const glm::vec2 itemMargin = elem->get("itemMargin"); - if (itemMargin.x < 0.0f || itemMargin.x > 0.2f || itemMargin.y < 0.0f || - itemMargin.y > 0.2f) { + glm::vec2 itemMargin = elem->get("itemMargin"); + if ((itemMargin.x != -1.0 && itemMargin.y != -1.0) && + (itemMargin.x < 0.0f || itemMargin.x > 0.2f || itemMargin.y < 0.0f || + itemMargin.y > 0.2f)) { LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" << itemMargin.x << " " << itemMargin.y << "\""; @@ -149,26 +224,67 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, } } + if (elem->has("controllerPos")) { + const glm::vec2 controllerPos = elem->get("controllerPos"); + if (controllerPos.x < -1.0f || controllerPos.x > 2.0f || controllerPos.y < -1.0f || + controllerPos.y > 2.0f) { + LOG(LogWarning) + << "BadgesComponent: Invalid theme configuration, set to \"" + << controllerPos.x << " " << controllerPos.y << "\""; + } + else { + mFlexboxComponent.setOverlayPosition(controllerPos); + } + } + + if (elem->has("controllerSize")) { + const float controllerSize = elem->get("controllerSize"); + if (controllerSize < 0.1f || controllerSize > 2.0f) { + LOG(LogWarning) + << "BadgesComponent: Invalid theme configuration, set to \"" + << controllerSize << "\""; + } + else { + mFlexboxComponent.setOverlaySize(controllerSize); + } + } + if (elem->has("slots")) { - std::vector slots = Utils::String::delimitedStringToVector( - Utils::String::toLower(elem->get("slots")), " "); + // Replace possible whitespace separators with commas. + std::string slotsTag = Utils::String::toLower(elem->get("slots")); + for (auto& character : slotsTag) { + if (std::isspace(character)) + character = ','; + } + slotsTag = Utils::String::replace(slotsTag, ",,", ","); + std::vector slots = Utils::String::delimitedStringToVector(slotsTag, ","); for (auto slot : slots) { if (std::find(mBadgeTypes.cbegin(), mBadgeTypes.cend(), slot) != mBadgeTypes.end()) { if (properties & PATH && elem->has(slot)) mBadgeIcons[slot] = elem->get(slot); - ImageComponent badgeImage{mWindow}; + FlexboxComponent::FlexboxItem item; + item.label = slot; + ImageComponent badgeImage{mWindow}; + badgeImage.setForceLoad(true); badgeImage.setImage(mBadgeIcons[slot]); - badgeImage.setVisible(false); - mBadgeImages.push_back(std::make_pair(slot, badgeImage)); + item.baseImage = badgeImage; + item.overlayImage = ImageComponent{mWindow}; + + mFlexboxItems.push_back(item); } else { LOG(LogError) << "Invalid badge slot \"" << slot << "\" defined"; } } + for (auto& controllerType : sControllerTypes) { + if (properties & PATH && elem->has(controllerType.shortName)) + controllerType.fileName = elem->get(controllerType.shortName); + } + GuiComponent::applyTheme(theme, view, element, properties); mFlexboxComponent.setPosition(mPosition); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 12cbd5927..a064966eb 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -13,13 +13,34 @@ #include "FlexboxComponent.h" #include "GuiComponent.h" +struct ControllerTypes { + std::string shortName; + std::string displayName; + std::string fileName; +}; + class BadgesComponent : public GuiComponent { public: BadgesComponent(Window* window); + struct BadgeInfo { + std::string badgeType; + std::string controllerType; + }; + + static void populateControllerTypes(); std::vector getBadgeTypes() { return mBadgeTypes; } - void setBadges(const std::vector& badges); + void setBadges(const std::vector& badges); + static const std::vector& getControllerTypes() + { + if (sControllerTypes.empty()) + populateControllerTypes(); + return sControllerTypes; + } + + static const std::string getShortName(const std::string& displayName); + static const std::string getDisplayName(const std::string& shortName); void render(const glm::mat4& parentTrans) override; void onSizeChanged() override { mFlexboxComponent.onSizeChanged(); } @@ -30,11 +51,13 @@ public: unsigned int properties) override; private: + static std::vector sControllerTypes; + + std::vector mFlexboxItems; FlexboxComponent mFlexboxComponent; std::vector mBadgeTypes; std::map mBadgeIcons; - std::vector> mBadgeImages; }; #endif // ES_CORE_COMPONENTS_BADGES_COMPONENT_H diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 4979de675..da3bf1b1c 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -19,16 +19,17 @@ #include "Settings.h" #include "ThemeData.h" -FlexboxComponent::FlexboxComponent(Window* window, - std::vector>& images) +FlexboxComponent::FlexboxComponent(Window* window, std::vector& items) : GuiComponent{window} - , mImages(images) + , mItems(items) , mDirection{DEFAULT_DIRECTION} , mAlignment{DEFAULT_ALIGNMENT} , mItemsPerLine{DEFAULT_ITEMS_PER_LINE} , mLines{DEFAULT_LINES} , mItemPlacement{DEFAULT_ITEM_PLACEMENT} , mItemMargin{glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}} + , mOverlayPosition{0.5f, 0.5f} + , mOverlaySize{0.5f} , mLayoutValid{false} { } @@ -45,35 +46,47 @@ void FlexboxComponent::render(const glm::mat4& parentTrans) Renderer::setMatrix(trans); if (Settings::getInstance()->getBool("DebugImage")) - Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); + Renderer::drawRect(0.0f, 0.0f, ceilf(mSize.x), ceilf(mSize.y), 0xFF000033, 0xFF000033); - for (auto& image : mImages) { + for (auto& item : mItems) { + if (!item.visible) + continue; if (mOpacity == 255) { - image.second.render(trans); + item.baseImage.render(trans); + if (item.overlayImage.getTexture() != nullptr) + item.overlayImage.render(trans); } else { - image.second.setOpacity(mOpacity); - image.second.render(trans); - image.second.setOpacity(255); + item.baseImage.setOpacity(mOpacity); + item.baseImage.render(trans); + item.baseImage.setOpacity(255); + if (item.overlayImage.getTexture() != nullptr) { + item.overlayImage.setOpacity(mOpacity); + item.overlayImage.render(trans); + item.overlayImage.setOpacity(255); + } } } } +void FlexboxComponent::setItemMargin(glm::vec2 value) +{ + if (value.x == -1.0f) + mItemMargin.x = std::roundf(value.y * Renderer::getScreenHeight()); + else + mItemMargin.x = std::roundf(value.x * Renderer::getScreenWidth()); + + if (value.y == -1.0f) + mItemMargin.y = std::roundf(value.x * Renderer::getScreenWidth()); + else + mItemMargin.y = std::roundf(value.y * Renderer::getScreenHeight()); + + mLayoutValid = false; +} + void FlexboxComponent::computeLayout() { - // Start placing items in the top-left. - float anchorX{0.0f}; - float anchorY{0.0f}; - - // Translation directions when placing items. - glm::vec2 directionLine{1, 0}; - glm::vec2 directionRow{0, 1}; - - // Change direction. - if (mDirection == "column") { - directionLine = {0, 1}; - directionRow = {1, 0}; - } + // TODO: There is no right-alignment support for column mode. // If we're not clamping itemMargin to a reasonable value, all kinds of weird rendering // issues could occur. @@ -86,125 +99,146 @@ void FlexboxComponent::computeLayout() mSize.y = glm::clamp(mSize.y, static_cast(Renderer::getScreenHeight()) * 0.03f, static_cast(Renderer::getScreenHeight())); - // Compute maximum image dimensions. - glm::vec2 grid; - if (mDirection == "row") - grid = {mItemsPerLine, mLines}; - else - grid = {mLines, mItemsPerLine}; + if (mItemsPerLine * mLines < mItems.size()) { + LOG(LogWarning) + << "FlexboxComponent: Invalid theme configuration, the number of badges" + " exceeds the product of times , setting to " + << mItems.size(); + mItemsPerLine = static_cast(mItems.size()); + } + glm::vec2 grid{mItemsPerLine, mLines}; glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid}; - maxItemSize.x = floorf(maxItemSize.x); - maxItemSize.y = floorf(maxItemSize.y); - if (grid.x * grid.y < static_cast(mImages.size())) { - LOG(LogWarning) << "FlexboxComponent: Invalid theme configuration, the number of badges " - "exceeds the product of times "; - } + float rowHeight{0.0f}; + bool firstItem{true}; - glm::vec2 sizeChange{0.0f, 0.0f}; - - // Set final image dimensions. - for (auto& image : mImages) { - if (!image.second.isVisible()) - continue; - auto oldSize{image.second.getSize()}; - if (oldSize.x == 0) - oldSize.x = maxItemSize.x; - glm::vec2 sizeMaxX{maxItemSize.x, oldSize.y * (maxItemSize.x / oldSize.x)}; - glm::vec2 sizeMaxY{oldSize.x * (maxItemSize.y / oldSize.y), maxItemSize.y}; - glm::vec2 newSize; - if (sizeMaxX.y > maxItemSize.y) - newSize = sizeMaxY; - else if (sizeMaxY.x > maxItemSize.x) - newSize = sizeMaxX; - else - newSize = sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY; - - if (image.second.getSize() != newSize) - image.second.setResize(std::round(newSize.x), std::round(newSize.y)); - - // In case maxItemSize needs to be updated. - if (newSize.x != sizeChange.x) - sizeChange.x = newSize.x; - if (newSize.y != sizeChange.y) - sizeChange.y = newSize.y; - } - - if (maxItemSize.x != sizeChange.x) - maxItemSize.x = std::round(sizeChange.x); - if (maxItemSize.y != sizeChange.y) - maxItemSize.y = std::round(sizeChange.y); - - // Pre-compute layout parameters. - float anchorXStart{anchorX}; - float anchorYStart{anchorY}; - - int i = 0; - - // Iterate through the images. - for (auto& image : mImages) { - if (!image.second.isVisible()) + // Calculate maximum item dimensions. + for (auto& item : mItems) { + if (!item.visible) continue; - auto size{image.second.getSize()}; + glm::vec2 sizeDiff{item.baseImage.getSize() / maxItemSize}; - // Top-left anchor position. - float x{anchorX}; - float y{anchorY}; - - // Apply alignment. - if (mItemPlacement == "end") { - x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0; - y += directionLine.y == 0 ? (maxItemSize.y - size.y) : 0; - } - else if (mItemPlacement == "center") { - x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0; - y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; - } - else if (mItemPlacement == "stretch" && mDirection == "row") { - image.second.setSize(image.second.getSize().x, maxItemSize.y); - } - - // Apply overall container alignment. - if (mAlignment == "right") - x += (mSize.x - maxItemSize.x * grid.x - (grid.x - 1) * mItemMargin.x); - - // Store final item position. - image.second.setPosition(x, y); - - // Translate anchor. - if ((i++ + 1) % std::max(1, static_cast(mItemsPerLine)) != 0) { - // Translate on same line. - anchorX += (size.x + mItemMargin.x) * static_cast(directionLine.x); - anchorY += (size.y + mItemMargin.y) * static_cast(directionLine.y); + // The first item dictates the maximum width for the rest. + if (firstItem) { + maxItemSize.x = (item.baseImage.getSize() / std::max(sizeDiff.x, sizeDiff.y)).x; + sizeDiff = item.baseImage.getSize() / maxItemSize; + item.baseImage.setSize((item.baseImage.getSize() / std::max(sizeDiff.x, sizeDiff.y))); + firstItem = false; } else { - // Translate to first position of next line. - if (directionRow.x == 0) { - anchorY += size.y + mItemMargin.y; - anchorX = anchorXStart; + item.baseImage.setSize((item.baseImage.getSize() / std::max(sizeDiff.x, sizeDiff.y))); + } + + if (item.baseImage.getSize().y > rowHeight) + rowHeight = item.baseImage.getSize().y; + } + + // Update the maximum item height. + maxItemSize.y = 0.0f; + for (auto& item : mItems) { + if (!item.visible) + continue; + if (item.baseImage.getSize().y > maxItemSize.y) + maxItemSize.y = item.baseImage.getSize().y; + } + + maxItemSize = glm::round(maxItemSize); + + bool alignRight{mAlignment == "right" && mDirection == "row"}; + float alignRightComp{0.0f}; + + // If right-aligning, move the overall container contents during grid setup. + if (alignRight) + alignRightComp = + std::round(mSize.x - ((maxItemSize.x + mItemMargin.x) * grid.x) + mItemMargin.x); + + std::vector itemPositions; + + // Lay out the grid. + if (mDirection == "row") { + for (int y = 0; y < grid.y; y++) { + for (int x = 0; x < grid.x; x++) { + itemPositions.push_back( + glm::vec2{(x * (maxItemSize.x + mItemMargin.x) + alignRightComp), + y * (rowHeight + mItemMargin.y)}); } - else { - anchorX += size.x + mItemMargin.x; - anchorY = anchorYStart; + } + } + else { // Column mode. + for (int x = 0; x < grid.x; x++) { + for (int y = 0; y < grid.y; y++) { + itemPositions.push_back( + glm::vec2{(x * (maxItemSize.x + mItemMargin.x) + alignRightComp), + y * (rowHeight + mItemMargin.y)}); } } } - // Apply right-align to the images on the last row, if needed. - if (mAlignment == "right") { - std::vector imagesToAlign; - for (auto& image : mImages) { - if (!image.second.isVisible()) - continue; - // Only include images on the last row. - if (image.second.getPosition().y == anchorY) - imagesToAlign.push_back(&image.second); + int pos{0}; + float lastY{0.0f}; + float itemsOnLastRow{0}; + + // Position items on the grid. + for (auto& item : mItems) { + if (!item.visible) + continue; + + if (pos > 0) { + if (itemPositions[pos - 1].y < itemPositions[pos].y) { + lastY = itemPositions[pos].y; + itemsOnLastRow = 0; + } } - for (auto& moveImage : imagesToAlign) { - float offset = (maxItemSize.x + mItemMargin.x) * (grid.x - imagesToAlign.size()); - moveImage->setPosition(moveImage->getPosition().x + offset, moveImage->getPosition().y); + + float verticalOffset{0.0f}; + + // For any items that do not fill the maximum height, position these either on + // top/start (implicit), center or bottom/end. + if (item.baseImage.getSize().y < maxItemSize.y) { + if (mItemPlacement == "center") { + verticalOffset = std::floor((maxItemSize.y - item.baseImage.getSize().y) / 2.0f); + } + else if (mItemPlacement == "end") { + verticalOffset = maxItemSize.y - item.baseImage.getSize().y; + } + } + + item.baseImage.setPosition(itemPositions[pos].x, itemPositions[pos].y + verticalOffset, + 0.0f); + + // Optional overlay image. + if (item.overlayImage.getTexture() != nullptr) { + item.overlayImage.setResize(item.baseImage.getSize().x * mOverlaySize, 0.0f); + item.overlayImage.setPosition( + item.baseImage.getPosition().x + (item.baseImage.getSize().x * mOverlayPosition.x) - + item.overlayImage.getSize().x / 2.0f, + item.baseImage.getPosition().y + (item.baseImage.getSize().y * mOverlayPosition.y) - + item.overlayImage.getSize().y / 2.0f); + } + + // This rasterizes the SVG images so they look nice and smooth. + item.baseImage.setResize(item.baseImage.getSize()); + + itemsOnLastRow++; + pos++; + } + + // Apply right-align to the items (only works in row mode). + if (alignRight) { + for (auto& item : mItems) { + if (!item.visible) + continue; + glm::vec3 currPos{item.baseImage.getPosition()}; + if (currPos.y == lastY) { + const float offset{(grid.x - itemsOnLastRow) * (maxItemSize.x + mItemMargin.x)}; + item.baseImage.setPosition(currPos.x + offset, currPos.y, currPos.z); + if (item.overlayImage.getTexture() != nullptr) { + currPos = item.overlayImage.getPosition(); + item.overlayImage.setPosition(currPos.x + offset, currPos.y, currPos.z); + } + } } } diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index 41c24433e..4ab5ee5c3 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -10,12 +10,23 @@ #define ES_CORE_COMPONENTS_FLEXBOX_COMPONENT_H #include "GuiComponent.h" +#include "Window.h" #include "components/ImageComponent.h" class FlexboxComponent : public GuiComponent { public: - FlexboxComponent(Window* window, std::vector>& images); + struct FlexboxItem { + // Optional label, mostly a convenience for the calling class to track items. + std::string label; + // Main image that governs grid sizing and placement. + ImageComponent baseImage{nullptr}; + // Optional overlay image that can be sized and positioned relative to the base image. + ImageComponent overlayImage{nullptr}; + bool visible = false; + }; + + FlexboxComponent(Window* window, std::vector& items); // Getters/setters for the layout. std::string getDirection() const { return mDirection; } @@ -56,12 +67,13 @@ public: } glm::vec2 getItemMargin() const { return mItemMargin; } - void setItemMargin(glm::vec2 value) - { - mItemMargin.x = std::roundf(value.x * Renderer::getScreenWidth()); - mItemMargin.y = std::roundf(value.y * Renderer::getScreenHeight()); - mLayoutValid = false; - } + void setItemMargin(glm::vec2 value); + + glm::vec2 getOverlayPosition() const { return mOverlayPosition; } + void setOverlayPosition(glm::vec2 position) { mOverlayPosition = position; } + + float getOverlaySize() const { return mOverlaySize; } + void setOverlaySize(float size) { mOverlaySize = size; } void onSizeChanged() override { mLayoutValid = false; } void render(const glm::mat4& parentTrans) override; @@ -70,7 +82,7 @@ private: // Calculate flexbox layout. void computeLayout(); - std::vector>& mImages; + std::vector& mItems; // Layout options. std::string mDirection; @@ -80,6 +92,9 @@ private: std::string mItemPlacement; glm::vec2 mItemMargin; + glm::vec2 mOverlayPosition; + float mOverlaySize; + bool mLayoutValid; }; diff --git a/resources/graphics/badge_controller.svg b/resources/graphics/badge_controller.svg new file mode 100644 index 000000000..53faecd2f --- /dev/null +++ b/resources/graphics/badge_controller.svg @@ -0,0 +1,147 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_generic.svg b/resources/graphics/controllers/gamepad_generic.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/gamepad_generic.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_nintendo_64.svg b/resources/graphics/controllers/gamepad_nintendo_64.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/gamepad_nintendo_64.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_nintendo_nes.svg b/resources/graphics/controllers/gamepad_nintendo_nes.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/gamepad_nintendo_nes.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_nintendo_snes.svg b/resources/graphics/controllers/gamepad_nintendo_snes.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/gamepad_nintendo_snes.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_playstation.svg b/resources/graphics/controllers/gamepad_playstation.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/gamepad_playstation.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_xbox.svg b/resources/graphics/controllers/gamepad_xbox.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/gamepad_xbox.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/joycon_left_or_right_nintendo.svg b/resources/graphics/controllers/joycon_left_or_right_nintendo.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/joycon_left_or_right_nintendo.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/joycon_pair_nintendo.svg b/resources/graphics/controllers/joycon_pair_nintendo.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/joycon_pair_nintendo.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/joystick_arcade_2_buttons.svg b/resources/graphics/controllers/joystick_arcade_2_buttons.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/joystick_arcade_2_buttons.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/joystick_arcade_3_buttons.svg b/resources/graphics/controllers/joystick_arcade_3_buttons.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/joystick_arcade_3_buttons.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/joystick_arcade_4_buttons.svg b/resources/graphics/controllers/joystick_arcade_4_buttons.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/joystick_arcade_4_buttons.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/joystick_arcade_6_buttons.svg b/resources/graphics/controllers/joystick_arcade_6_buttons.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/joystick_arcade_6_buttons.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/joystick_generic.svg b/resources/graphics/controllers/joystick_generic.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/joystick_generic.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/keyboard_generic.svg b/resources/graphics/controllers/keyboard_generic.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/keyboard_generic.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/keyboard_mouse_generic.svg b/resources/graphics/controllers/keyboard_mouse_generic.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/keyboard_mouse_generic.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/lightgun_generic.svg b/resources/graphics/controllers/lightgun_generic.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/lightgun_generic.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/lightgun_nintendo.svg b/resources/graphics/controllers/lightgun_nintendo.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/lightgun_nintendo.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/mouse_amiga.svg b/resources/graphics/controllers/mouse_amiga.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/mouse_amiga.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/mouse_generic.svg b/resources/graphics/controllers/mouse_generic.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/mouse_generic.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/steering_wheel_generic.svg b/resources/graphics/controllers/steering_wheel_generic.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/steering_wheel_generic.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/trackball_generic.svg b/resources/graphics/controllers/trackball_generic.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/trackball_generic.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/unknown.svg b/resources/graphics/controllers/unknown.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/unknown.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/wii_remote_nintendo.svg b/resources/graphics/controllers/wii_remote_nintendo.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/wii_remote_nintendo.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/graphics/controllers/wii_remote_nunchuck_nintendo.svg b/resources/graphics/controllers/wii_remote_nunchuck_nintendo.svg new file mode 100644 index 000000000..b6b01c15a --- /dev/null +++ b/resources/graphics/controllers/wii_remote_nunchuck_nintendo.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 690d7f0c4..130467fbc 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -237,14 +237,16 @@ based on: 'recalbox-multi' by the Recalbox community right - 0.815 0.675 + 0.880 0.757 0.13 0.1635 - 0 0 + 0.5 0.5 left 3 2 - 0.0028125 0.005 - favorite completed kidgame broken altemulator + 0.5 0.572 + 0.67 + -1.0 0.005 + favorite, completed, kidgame, broken, controller, altemulator From 9c8a5ca5d3ab0b8f049889b01508c601440a5c7f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 19:30:58 +0200 Subject: [PATCH 095/128] Documentation update. --- CHANGELOG.md | 4 ++++ THEMES-DEV.md | 16 ++++++++++++---- USERGUIDE-DEV.md | 6 +++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f21da3ca2..9d802b212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * Populated the bundled es_systems.xml files with alternative emulator entries for most RetroArch cores * Added a virtual keyboard, partly based on code from batocera-emulationstation * Added badges that indicate favorite/completed/broken games as well as games suitable for children and those with a selected alternative emulator +* Added controller types, selectable via the metadata editor and displayed as a controller badge * Added the ability to make complementary game system customizations without having to replace the entire bundled es_systems.xml file * Added support for an optional \ tag for es_systems.xml that can be used to override the default \ systems sorting * Added menu scroll indicators showing if there are additional entries available below or above what's currently shown on screen @@ -80,6 +81,7 @@ * Under some circumstances ScrollableContainer (used for the game descriptions) would contain a partially rendered bottom line * If the TextListComponent height was not evenly dividable by the font height + line spacing, a partial bottom row would get rendered * The line spacing for TextListComponent was incorrectly calculated for some resolutions such as 2560x1440 +* Fixed multiple issues with scaling of images which lead to various inconsistencies and sometimes cut-off graphics * Removing games from custom collections did not remove their filter index entries * Input consisting of only whitespace characters would get accepted by TextEditComponent which led to various strange behaviors * Leading and trailing whitespace characters would not get trimmed from the collection name when creating a new custom collection @@ -98,6 +100,8 @@ * Really long theme set names would not get abbreviated in the UI settings menu, leading to a garbled "Theme set" setting row * Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a gamelist on startup had been set * When marking a game to not be counted in the metadata editor and the game was part of a custom collection, no collection disabling notification was displayed +* SliderComponent did not properly align the knob and bar vertically +* Resizing in SwitchComponent did not reposition the image properly leading to a non-centered image * Horizontal sizing of the TextComponent input field was not consistent across different screen resolutions * The sizing of the metadata editor was strange, which was clearly visible when activating the Ctrl+G debug mode * The "sortname" window header was incorrectly spelled when editing this type of entry in the metadata editor diff --git a/THEMES-DEV.md b/THEMES-DEV.md index e13aea2c4..6d078639e 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -910,6 +910,8 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren #### badges +It's strongly recommended to use the same image dimensions for all badges as varying aspect ratios will lead to alignment issues. For the controller images it's recommended to keep to the square canvas size used by the default bundled graphics as otherwise sizing and placement will be inconsistent (unless all controller graphic files are customized of course). Overall it's a very good idea to keep the image dimensions small. This is especially true for SVG graphics as rasterization will otherwise take a long time which will slow down application startup and gamelist navigation. + * `pos` - type: NORMALIZED_PAIR. * `size` - type: NORMALIZED_PAIR. - Possible combinations: @@ -924,20 +926,26 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren - Number of badges that fit on a row. When more badges are available a new row will be started. Default is `4`. * `rows` - type: FLOAT. - The number of rows available. Default is `2`. -* `itemPlacement` - type: STRING. - - Valid values are "top", "center", "bottom", or "stretch". Controls vertical alignment of each badge if images of different heights are used. "Stretch" will stretch the badge to the full height. Default is `center`. * `itemMargin` - type: NORMALIZED_PAIR. - The margins between badges. Possible combinations: - - `x y` - horizontal and vertical margins. Minimum value per axis is `0`, maximum value is `0.2`. Default is `0.01`. + - `x y` - horizontal and vertical margins. Minimum value per axis is `0`, maximum value is `0.2`. Default is `0.01 0.01`. If one of the axis is set to `-1` the margin of the other axis (in pixels) will be used, which makes it possible to get identical spacing between all items regardless of screen aspect ratio. * `slots` - type: STRING. - - The badge types that should be displayed. Should be specified as a list of strings separated by spaces. The order will be followed when placing badges on the screen. Available badges are: + - The badge types that should be displayed. Should be specified as a list of strings delimited by any characters of `\t\r\n ,` - that is, whitespace and commas. The order will be followed when placing badges on the screen. Available badges are: - `favorite`: Will be shown when the game is marked as favorite. - `completed`: Will be shown when the game is marked as completed. - `kidgame`: Will be shown when the game is marked as a kids game. - `broken`: Will be shown when the game is marked as broken. + - `controller`: Will be shown and overlaid by the corresponding controller icon if a controller type has been selected for the game using the metadata editor. - `altemulator`: Will be shown when an alternative emulator is setup for the game. +* `controllerPos` - type: NORMALIZED_PAIR. + - The position of the controller icon relative to the parent `controller` badge. Minimum value per axis is `-1.0`, maximum value is `2.0`. Default is `0.5 0.5` which centers the controller icon on the badge. +* `controllerSize` - type: FLOAT. + - The size of the controller icon relative to the parent `controller` badge. Minimum value is `0.1`, maximum value is `2.0`. Setting the value to `1.0` sizes the controller icon to the same width as the parent badge. The image aspect ratio is always maintained. * `customBadgeIcon` - type: PATH. - A badge icon override. Specify the badge type in the attribute `badge`. The available badges are the ones listed above. +* `customControllerIcon` - type: PATH. + - A controller icon override. Specify the controller type in the attribute `controller`. These are the available types: + - `gamepad_generic`, `gamepad_xbox`, `gamepad_playstation`, `gamepad_nintendo_nes`, `gamepad_nintendo_snes`, `gamepad_nintendo_64`, `joystick_generic`, `joystick_arcade_2_buttons`, `joystick_arcade_3_buttons`, `joystick_arcade_4_buttons`, `joystick_arcade_6_buttons`, `trackball_generic`, `lightgun_generic`, `lightgun_nintendo`, `keyboard_generic`, `mouse_generic`, `mouse_amiga`, `keyboard_mouse_generic`, `steering_wheel_generic`, `wii_remote_nintendo`, `wii_remote_nunchuck_nintendo`, `joycon_left_or_right_nintendo`, `joycon_pair_nintendo`, `unknown`. * `visible` - type: BOOLEAN. - If true, component will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. * `zIndex` - type: FLOAT. diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 6c944394a..7ce7db6eb 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -215,7 +215,7 @@ In addition to the styles just described, there is a **Grid** view style as well If the theme supports it, there's a gamelist information field displayed in the gamelist view, showing the number of games for the system (total and favorites) as well as a folder icon if a folder has been entered. When applying any filters to the gamelist, the game counter is replaced with the amount of games filtered, as in 'filtered / total games', e.g. '19 / 77'. If there are game entries in the filter result that are marked not to be counted as games, the number of such files will be indicated as 'filtered + filtered non-games / total games', for example '23 + 4 / 77' indicating 23 normal games, 4 non-games out of a total of 77. Due to this approach it's theoretically possible that the combined filtered game amount exceeds the number of counted games in the collection, for instance '69 + 11 / 77'. This is not considered a bug and is so by design. This gamelist information field functionality is specific to EmulationStation Desktop Edition so older themes will not support this. -Another feature which requires theme support is **Badges**, which is a set of icons displaying the status for various metadata fields. The currently supported badge types are _favorite, completed, kidgame, broken_ and _alternative emulator_. If any of the first four metadata fields have been set for a game, their corresponding badges will be displayed, and if an alternative emulator has been selected for the specific game, that badge will be shown. Setting an alternative emulator system-wide will not display this badge as it's only intended to indicate game-specific overrides. +Another feature which requires theme support is **Badges**, which is a set of icons displaying the status for various metadata fields. The currently supported badge types are _favorite, completed, kidgame, broken, controller_ and _alternative emulator_. If any of the first four metadata fields have been set for a game, their corresponding badges will be displayed. If a controller type has been selected for the game, the corresponding controller icon will be shown on the controller badge, and if an alternative emulator has been selected for the specific game, that badge will be shown. Setting an alternative emulator system-wide will not display this badge as it's only intended to indicate game-specific overrides. ![alt text](images/es-de_gamelist_view.png "ES-DE Gamelist View") _The **Gamelist view** is where you browse the games for a specific system._ @@ -1476,6 +1476,10 @@ This option will hide most metadata fields as well as any badges. The intention A statistics counter that tracks how many times you have played the game. You normally don't need to touch this, but if you want to, the possibility is there. +**Controller type** + +Contains a list of controller types that are built into ES-DE. The selected controller will be displayed as a badge if the current theme set support badges. This functionality is only cosmetic and will not affect the actual emulators. + **Alternative emulator** _(files only)_ If the option _Enable alternative emulators per game_ has been enabled, there will be an entry shown where you can select between alternative emulators for the specific game. There is a similar _Alternative emulators_ entry under the _Other settings_ menu, but that will apply the selection to the entire game system. If you select an alternative for a specific game using the metadata editor, that will take precedence and override any system-wide emulator selection (the currently selected system-wide emulator will be clearly marked on the selection screen). The alternative emulators need to be defined in the es_systems.xml file, and if there are no alternatives available for the current system, this row in the metadata editor will be grayed out. If you select an alternative emulator and later remove its corresponding entry from the es_systems.xml file, an error notice will be shown on this row. In this case you have the option to remove the invalid entry. But even if there is an invalid entry, games will still launch using the default emulator while logging a warning message to the es_log.txt file. Apart from this, the emulator selection should hopefully be self-explanatory. From b5a61a0617d9ec01b5818511f8c2fc851e09ddcb Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 20:14:17 +0200 Subject: [PATCH 096/128] Renamed 'controller types' to 'game controllers' (code) and simply 'controllers' (metadata editor). --- es-app/src/MetaData.cpp | 4 +- es-app/src/guis/GuiMetaDataEd.cpp | 16 +++--- es-app/src/guis/GuiMetaDataEd.h | 2 +- .../views/gamelist/DetailedGameListView.cpp | 2 +- .../src/views/gamelist/VideoGameListView.cpp | 2 +- es-core/src/components/BadgesComponent.cpp | 52 +++++++++---------- es-core/src/components/BadgesComponent.h | 16 +++--- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 84f52233d..943099fcb 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -37,7 +37,7 @@ MetaDataDecl gameDecls[] = { {"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, {"playcount", MD_INT, "0", false, "times played", "enter number of times played", false}, -{"controller", MD_CONTROLLER, "", false, "controller type", "select controller type", false}, +{"controller", MD_CONTROLLER, "", false, "controller", "select controller", false}, {"altemulator", MD_ALT_EMULATOR, "", false, "alternative emulator", "select alternative emulator", false}, {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; @@ -58,7 +58,7 @@ MetaDataDecl folderDecls[] = { {"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, {"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, -{"controller", MD_CONTROLLER, "", false, "controller type", "select controller type", false}, +{"controller", MD_CONTROLLER, "", false, "controller", "select controller", false}, {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; // clang-format on diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 42d9c52a9..24e755f6a 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -53,11 +53,11 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, , mMediaFilesUpdated{false} , mInvalidEmulatorEntry{false} { - mControllerTypes = BadgesComponent::getControllerTypes(); + mGameControllers = BadgesComponent::getGameControllers(); // Remove the last "unknown" controller entry. - if (mControllerTypes.size() > 1) - mControllerTypes.pop_back(); + if (mGameControllers.size() > 1) + mGameControllers.pop_back(); addChild(&mBackground); addChild(&mGrid); @@ -210,7 +210,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, originalValue] { GuiSettings* s = new GuiSettings(mWindow, title); - for (auto controllerType : mControllerTypes) { + for (auto controller : mGameControllers) { std::string selectedLabel = ed->getValue(); std::string label; ComponentListRow row; @@ -218,14 +218,14 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, std::shared_ptr labelText = std::make_shared( mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF); labelText->setSelectable(true); - labelText->setValue(controllerType.displayName); + labelText->setValue(controller.displayName); - label = controllerType.displayName; + label = controller.displayName; row.addElement(labelText, true); - row.makeAcceptInputHandler([s, updateVal, controllerType] { - updateVal(controllerType.displayName); + row.makeAcceptInputHandler([s, updateVal, controller] { + updateVal(controller.displayName); delete s; }); diff --git a/es-app/src/guis/GuiMetaDataEd.h b/es-app/src/guis/GuiMetaDataEd.h index e73714e8d..054c112f3 100644 --- a/es-app/src/guis/GuiMetaDataEd.h +++ b/es-app/src/guis/GuiMetaDataEd.h @@ -61,7 +61,7 @@ private: ScraperSearchParams mScraperParams; - std::vector mControllerTypes; + std::vector mGameControllers; std::vector> mEditors; std::vector mMetaDataDecl; diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index a47f65f98..9fc1d6805 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -416,7 +416,7 @@ void DetailedGameListView::updateInfoPanel() badgeInfo.badgeType = badge; if (badge == "controller") { if (file->metadata.get("controller").compare("") != 0) { - badgeInfo.controllerType = file->metadata.get("controller"); + badgeInfo.gameController = file->metadata.get("controller"); badgeSlots.push_back(badgeInfo); } } diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 699c253f8..8965def88 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -456,7 +456,7 @@ void VideoGameListView::updateInfoPanel() badgeInfo.badgeType = badge; if (badge == "controller") { if (file->metadata.get("controller").compare("") != 0) { - badgeInfo.controllerType = file->metadata.get("controller"); + badgeInfo.gameController = file->metadata.get("controller"); badgeSlots.push_back(badgeInfo); } } diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index b932fddea..8236593a0 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -20,12 +20,12 @@ #include "ThemeData.h" #include "utils/StringUtil.h" -std::vector BadgesComponent::sControllerTypes; +std::vector BadgesComponent::sGameControllers; // clang-format off // The "unknown" controller entry has to be placed last. -ControllerTypes sControllerDefinitions [] = { +GameControllers sControllerDefinitions [] = { // shortName displayName fileName {"gamepad_generic", "Gamepad (Generic)", ":/graphics/controllers/gamepad_generic.svg"}, {"gamepad_xbox", "Gamepad (Xbox)", ":/graphics/controllers/gamepad_xbox.svg"}, @@ -70,11 +70,11 @@ BadgesComponent::BadgesComponent(Window* window) mBadgeIcons[SLOT_ALTEMULATOR] = ":/graphics/badge_altemulator.svg"; } -void BadgesComponent::populateControllerTypes() +void BadgesComponent::populateGameControllers() { - sControllerTypes.clear(); - for (auto type : sControllerDefinitions) - sControllerTypes.push_back(type); + sGameControllers.clear(); + for (auto controller : sControllerDefinitions) + sGameControllers.push_back(controller); } void BadgesComponent::setBadges(const std::vector& badges) @@ -97,19 +97,19 @@ void BadgesComponent::setBadges(const std::vector& badges) if (it != mFlexboxItems.end()) { it->visible = true; - if (badge.controllerType != "" && - badge.controllerType != it->overlayImage.getTexture()->getTextureFilePath()) { + if (badge.gameController != "" && + badge.gameController != it->overlayImage.getTexture()->getTextureFilePath()) { - auto it2 = std::find_if(sControllerTypes.begin(), sControllerTypes.end(), - [badge](ControllerTypes controllerType) { - return controllerType.shortName == badge.controllerType; + auto it2 = std::find_if(sGameControllers.begin(), sGameControllers.end(), + [badge](GameControllers gameController) { + return gameController.shortName == badge.gameController; }); - if (it2 != sControllerTypes.cend()) { + if (it2 != sGameControllers.cend()) { it->overlayImage.setImage((*it2).fileName); } - else if (badge.controllerType != "") - it->overlayImage.setImage(sControllerTypes.back().fileName); + else if (badge.gameController != "") + it->overlayImage.setImage(sGameControllers.back().fileName); } } } @@ -126,11 +126,11 @@ void BadgesComponent::setBadges(const std::vector& badges) const std::string BadgesComponent::getShortName(const std::string& displayName) { - auto it = std::find_if(sControllerTypes.begin(), sControllerTypes.end(), - [displayName](ControllerTypes controllerType) { - return controllerType.displayName == displayName; + auto it = std::find_if(sGameControllers.begin(), sGameControllers.end(), + [displayName](GameControllers gameController) { + return gameController.displayName == displayName; }); - if (it != sControllerTypes.end()) + if (it != sGameControllers.end()) return (*it).shortName; else return "unknown"; @@ -138,11 +138,11 @@ const std::string BadgesComponent::getShortName(const std::string& displayName) const std::string BadgesComponent::getDisplayName(const std::string& shortName) { - auto it = std::find_if(sControllerTypes.begin(), sControllerTypes.end(), - [shortName](ControllerTypes controllerType) { - return controllerType.shortName == shortName; + auto it = std::find_if(sGameControllers.begin(), sGameControllers.end(), + [shortName](GameControllers gameController) { + return gameController.shortName == shortName; }); - if (it != sControllerTypes.end()) + if (it != sGameControllers.end()) return (*it).displayName; else return "unknown"; @@ -168,7 +168,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, const std::string& element, unsigned int properties) { - populateControllerTypes(); + populateGameControllers(); using namespace ThemeFlags; @@ -280,9 +280,9 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, } } - for (auto& controllerType : sControllerTypes) { - if (properties & PATH && elem->has(controllerType.shortName)) - controllerType.fileName = elem->get(controllerType.shortName); + for (auto& gameController : sGameControllers) { + if (properties & PATH && elem->has(gameController.shortName)) + gameController.fileName = elem->get(gameController.shortName); } GuiComponent::applyTheme(theme, view, element, properties); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index a064966eb..a87c34e2b 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -13,7 +13,7 @@ #include "FlexboxComponent.h" #include "GuiComponent.h" -struct ControllerTypes { +struct GameControllers { std::string shortName; std::string displayName; std::string fileName; @@ -26,17 +26,17 @@ public: struct BadgeInfo { std::string badgeType; - std::string controllerType; + std::string gameController; }; - static void populateControllerTypes(); + static void populateGameControllers(); std::vector getBadgeTypes() { return mBadgeTypes; } void setBadges(const std::vector& badges); - static const std::vector& getControllerTypes() + static const std::vector& getGameControllers() { - if (sControllerTypes.empty()) - populateControllerTypes(); - return sControllerTypes; + if (sGameControllers.empty()) + populateGameControllers(); + return sGameControllers; } static const std::string getShortName(const std::string& displayName); @@ -51,7 +51,7 @@ public: unsigned int properties) override; private: - static std::vector sControllerTypes; + static std::vector sGameControllers; std::vector mFlexboxItems; FlexboxComponent mFlexboxComponent; From 0351a2ccdbfc1ba3185bf5e545ad3802a2073c20 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 20:15:45 +0200 Subject: [PATCH 097/128] Small documentation update for the game controller functionality. --- CHANGELOG.md | 2 +- USERGUIDE-DEV.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d802b212..3069bc8f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ * Populated the bundled es_systems.xml files with alternative emulator entries for most RetroArch cores * Added a virtual keyboard, partly based on code from batocera-emulationstation * Added badges that indicate favorite/completed/broken games as well as games suitable for children and those with a selected alternative emulator -* Added controller types, selectable via the metadata editor and displayed as a controller badge +* Added game-specific controller images that are selectable via the metadata editor and displayed as a controller badge * Added the ability to make complementary game system customizations without having to replace the entire bundled es_systems.xml file * Added support for an optional \ tag for es_systems.xml that can be used to override the default \ systems sorting * Added menu scroll indicators showing if there are additional entries available below or above what's currently shown on screen diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 7ce7db6eb..483fb7a3b 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -215,7 +215,7 @@ In addition to the styles just described, there is a **Grid** view style as well If the theme supports it, there's a gamelist information field displayed in the gamelist view, showing the number of games for the system (total and favorites) as well as a folder icon if a folder has been entered. When applying any filters to the gamelist, the game counter is replaced with the amount of games filtered, as in 'filtered / total games', e.g. '19 / 77'. If there are game entries in the filter result that are marked not to be counted as games, the number of such files will be indicated as 'filtered + filtered non-games / total games', for example '23 + 4 / 77' indicating 23 normal games, 4 non-games out of a total of 77. Due to this approach it's theoretically possible that the combined filtered game amount exceeds the number of counted games in the collection, for instance '69 + 11 / 77'. This is not considered a bug and is so by design. This gamelist information field functionality is specific to EmulationStation Desktop Edition so older themes will not support this. -Another feature which requires theme support is **Badges**, which is a set of icons displaying the status for various metadata fields. The currently supported badge types are _favorite, completed, kidgame, broken, controller_ and _alternative emulator_. If any of the first four metadata fields have been set for a game, their corresponding badges will be displayed. If a controller type has been selected for the game, the corresponding controller icon will be shown on the controller badge, and if an alternative emulator has been selected for the specific game, that badge will be shown. Setting an alternative emulator system-wide will not display this badge as it's only intended to indicate game-specific overrides. +Another feature which requires theme support is **Badges**, which is a set of icons displaying the status for various metadata fields. The currently supported badge types are _favorite, completed, kidgame, broken, controller_ and _alternative emulator_. If any of the first four metadata fields have been set for a game, their corresponding badges will be displayed. If a game-specific controller has been selected, the corresponding controller icon will be shown on the controller badge, and if an alternative emulator has been selected for the specific game, that badge will be shown. Setting an alternative emulator system-wide will not display this badge as it's only intended to indicate game-specific overrides. ![alt text](images/es-de_gamelist_view.png "ES-DE Gamelist View") _The **Gamelist view** is where you browse the games for a specific system._ @@ -1476,9 +1476,9 @@ This option will hide most metadata fields as well as any badges. The intention A statistics counter that tracks how many times you have played the game. You normally don't need to touch this, but if you want to, the possibility is there. -**Controller type** +**Controller** -Contains a list of controller types that are built into ES-DE. The selected controller will be displayed as a badge if the current theme set support badges. This functionality is only cosmetic and will not affect the actual emulators. +Contains a list of controller images that are built into ES-DE. The selected controller will be displayed as a badge if the current theme set support badges. This functionality is only cosmetic and will not affect the actual emulators, and it will not affect the controller input for ES-DE itself. **Alternative emulator** _(files only)_ From 1aa360598dd7cce846055e96c38d036c52728f02 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 20:28:07 +0200 Subject: [PATCH 098/128] Renamed BadgesComponent to BadgeComponent. --- es-app/src/guis/GuiMetaDataEd.cpp | 10 ++--- es-app/src/guis/GuiMetaDataEd.h | 2 +- .../views/gamelist/DetailedGameListView.cpp | 4 +- .../src/views/gamelist/DetailedGameListView.h | 4 +- es-app/src/views/gamelist/GridGameListView.h | 4 +- .../src/views/gamelist/VideoGameListView.cpp | 4 +- es-app/src/views/gamelist/VideoGameListView.h | 4 +- es-core/CMakeLists.txt | 4 +- ...BadgesComponent.cpp => BadgeComponent.cpp} | 41 +++++++++---------- .../{BadgesComponent.h => BadgeComponent.h} | 12 +++--- 10 files changed, 44 insertions(+), 45 deletions(-) rename es-core/src/components/{BadgesComponent.cpp => BadgeComponent.cpp} (89%) rename es-core/src/components/{BadgesComponent.h => BadgeComponent.h} (86%) diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 24e755f6a..e46c1bb62 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -53,7 +53,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, , mMediaFilesUpdated{false} , mInvalidEmulatorEntry{false} { - mGameControllers = BadgesComponent::getGameControllers(); + mGameControllers = BadgeComponent::getGameControllers(); // Remove the last "unknown" controller entry. if (mGameControllers.size() > 1) @@ -200,7 +200,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, // OK callback (apply new value to ed). auto updateVal = [ed, originalValue](const std::string& newVal) { ed->setValue(newVal); - if (newVal == BadgesComponent::getDisplayName(originalValue)) + if (newVal == BadgeComponent::getDisplayName(originalValue)) ed->setColor(DEFAULT_TEXTCOLOR); else ed->setColor(TEXTCOLOR_USERMARKED); @@ -478,7 +478,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, ed->setValue(ViewController::EXCLAMATION_CHAR + " " + originalValue); } else if (iter->type == MD_CONTROLLER && mMetaData->get(iter->key) != "") { - std::string displayName = BadgesComponent::getDisplayName(mMetaData->get(iter->key)); + std::string displayName = BadgeComponent::getDisplayName(mMetaData->get(iter->key)); if (displayName != "unknown") ed->setValue(displayName); else @@ -618,7 +618,7 @@ void GuiMetaDataEd::save() continue; if (mMetaDataDecl.at(i).key == "controller" && mEditors.at(i)->getValue() != "") { - std::string shortName = BadgesComponent::getShortName(mEditors.at(i)->getValue()); + std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue()); if (shortName != "unknown") mMetaData->set(mMetaDataDecl.at(i).key, shortName); continue; @@ -769,7 +769,7 @@ void GuiMetaDataEd::close() continue; if (mMetaDataDecl.at(i).key == "controller" && mEditors.at(i)->getValue() != "") { - std::string shortName = BadgesComponent::getShortName(mEditors.at(i)->getValue()); + std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue()); if (shortName == "unknown" || mMetaDataValue == shortName) continue; } diff --git a/es-app/src/guis/GuiMetaDataEd.h b/es-app/src/guis/GuiMetaDataEd.h index 054c112f3..a4833c257 100644 --- a/es-app/src/guis/GuiMetaDataEd.h +++ b/es-app/src/guis/GuiMetaDataEd.h @@ -13,7 +13,7 @@ #include "GuiComponent.h" #include "MetaData.h" -#include "components/BadgesComponent.h" +#include "components/BadgeComponent.h" #include "components/ComponentGrid.h" #include "components/NinePatchComponent.h" #include "components/ScrollIndicatorComponent.h" diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 9fc1d6805..5a0846ff0 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -410,9 +410,9 @@ void DetailedGameListView::updateInfoPanel() mPlayers.setValue(file->metadata.get("players")); // Populate the badge slots based on game metadata. - std::vector badgeSlots; + std::vector badgeSlots; for (auto badge : mBadges.getBadgeTypes()) { - BadgesComponent::BadgeInfo badgeInfo; + BadgeComponent::BadgeInfo badgeInfo; badgeInfo.badgeType = badge; if (badge == "controller") { if (file->metadata.get("controller").compare("") != 0) { diff --git a/es-app/src/views/gamelist/DetailedGameListView.h b/es-app/src/views/gamelist/DetailedGameListView.h index a6033b2f6..ed4db80c2 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.h +++ b/es-app/src/views/gamelist/DetailedGameListView.h @@ -9,7 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H -#include "components/BadgesComponent.h" +#include "components/BadgeComponent.h" #include "components/DateTimeComponent.h" #include "components/RatingComponent.h" #include "components/ScrollableContainer.h" @@ -56,7 +56,7 @@ private: DateTimeComponent mLastPlayed; TextComponent mPlayCount; TextComponent mName; - BadgesComponent mBadges; + BadgeComponent mBadges; std::vector getMDLabels(); std::vector getMDValues(); diff --git a/es-app/src/views/gamelist/GridGameListView.h b/es-app/src/views/gamelist/GridGameListView.h index 2c2f50458..f84d52cf7 100644 --- a/es-app/src/views/gamelist/GridGameListView.h +++ b/es-app/src/views/gamelist/GridGameListView.h @@ -9,7 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H -#include "components/BadgesComponent.h" +#include "components/BadgeComponent.h" #include "components/DateTimeComponent.h" #include "components/ImageGridComponent.h" #include "components/RatingComponent.h" @@ -89,7 +89,7 @@ private: TextComponent mLblLastPlayed; TextComponent mLblPlayCount; - BadgesComponent mBadges; + BadgeComponent mBadges; RatingComponent mRating; DateTimeComponent mReleaseDate; TextComponent mDeveloper; diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index 8965def88..ef76d29e0 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -450,9 +450,9 @@ void VideoGameListView::updateInfoPanel() mPlayers.setValue(file->metadata.get("players")); // Populate the badge slots based on game metadata. - std::vector badgeSlots; + std::vector badgeSlots; for (auto badge : mBadges.getBadgeTypes()) { - BadgesComponent::BadgeInfo badgeInfo; + BadgeComponent::BadgeInfo badgeInfo; badgeInfo.badgeType = badge; if (badge == "controller") { if (file->metadata.get("controller").compare("") != 0) { diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index f5cae00d0..06d1c1034 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -9,7 +9,7 @@ #ifndef ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H -#include "components/BadgesComponent.h" +#include "components/BadgeComponent.h" #include "components/DateTimeComponent.h" #include "components/RatingComponent.h" #include "components/ScrollableContainer.h" @@ -60,7 +60,7 @@ private: DateTimeComponent mLastPlayed; TextComponent mPlayCount; TextComponent mName; - BadgesComponent mBadges; + BadgeComponent mBadges; std::vector getMDLabels(); std::vector getMDValues(); diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index 6949f2a32..d579317c6 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -34,7 +34,7 @@ set(CORE_HEADERS # GUI components ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgeComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h @@ -112,7 +112,7 @@ set(CORE_SOURCES # GUI components ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimatedImageComponent.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgesComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgeComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgeComponent.cpp similarity index 89% rename from es-core/src/components/BadgesComponent.cpp rename to es-core/src/components/BadgeComponent.cpp index 8236593a0..5402b62a9 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgeComponent.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // // EmulationStation Desktop Edition -// BadgesComponent.cpp +// BadgeComponent.cpp // // Game badges icons. // Used by the gamelist views. @@ -14,13 +14,13 @@ #define SLOT_CONTROLLER "controller" #define SLOT_ALTEMULATOR "altemulator" -#include "components/BadgesComponent.h" +#include "components/BadgeComponent.h" #include "Log.h" #include "ThemeData.h" #include "utils/StringUtil.h" -std::vector BadgesComponent::sGameControllers; +std::vector BadgeComponent::sGameControllers; // clang-format off @@ -55,7 +55,7 @@ GameControllers sControllerDefinitions [] = { // clang-format on -BadgesComponent::BadgesComponent(Window* window) +BadgeComponent::BadgeComponent(Window* window) : GuiComponent{window} , mFlexboxItems{} , mFlexboxComponent{window, mFlexboxItems} @@ -70,14 +70,14 @@ BadgesComponent::BadgesComponent(Window* window) mBadgeIcons[SLOT_ALTEMULATOR] = ":/graphics/badge_altemulator.svg"; } -void BadgesComponent::populateGameControllers() +void BadgeComponent::populateGameControllers() { sGameControllers.clear(); for (auto controller : sControllerDefinitions) sGameControllers.push_back(controller); } -void BadgesComponent::setBadges(const std::vector& badges) +void BadgeComponent::setBadges(const std::vector& badges) { std::map prevVisibility; std::map prevPlayers; @@ -124,7 +124,7 @@ void BadgesComponent::setBadges(const std::vector& badges) } } -const std::string BadgesComponent::getShortName(const std::string& displayName) +const std::string BadgeComponent::getShortName(const std::string& displayName) { auto it = std::find_if(sGameControllers.begin(), sGameControllers.end(), [displayName](GameControllers gameController) { @@ -136,7 +136,7 @@ const std::string BadgesComponent::getShortName(const std::string& displayName) return "unknown"; } -const std::string BadgesComponent::getDisplayName(const std::string& shortName) +const std::string BadgeComponent::getDisplayName(const std::string& shortName) { auto it = std::find_if(sGameControllers.begin(), sGameControllers.end(), [shortName](GameControllers gameController) { @@ -148,7 +148,7 @@ const std::string BadgesComponent::getDisplayName(const std::string& shortName) return "unknown"; } -void BadgesComponent::render(const glm::mat4& parentTrans) +void BadgeComponent::render(const glm::mat4& parentTrans) { if (!isVisible()) return; @@ -163,10 +163,10 @@ void BadgesComponent::render(const glm::mat4& parentTrans) } } -void BadgesComponent::applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, - unsigned int properties) +void BadgeComponent::applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) { populateGameControllers(); @@ -179,7 +179,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("alignment")) { const std::string alignment{elem->get("alignment")}; if (alignment != "left" && alignment != "right") { - LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" + LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" << alignment << "\""; } else { @@ -191,7 +191,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, const float itemsPerRow{elem->get("itemsPerRow")}; if (itemsPerRow < 1.0f || itemsPerRow > 10.0f) { LOG(LogWarning) - << "BadgesComponent: Invalid theme configuration, set to \"" + << "BadgeComponent: Invalid theme configuration, set to \"" << itemsPerRow << "\""; } else { @@ -202,7 +202,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("rows")) { const float rows{elem->get("rows")}; if (rows < 1.0f || rows > 10.0f) { - LOG(LogWarning) << "BadgesComponent: Invalid theme configuration, set to \"" + LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" << rows << "\""; } else { @@ -215,9 +215,8 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if ((itemMargin.x != -1.0 && itemMargin.y != -1.0) && (itemMargin.x < 0.0f || itemMargin.x > 0.2f || itemMargin.y < 0.0f || itemMargin.y > 0.2f)) { - LOG(LogWarning) - << "BadgesComponent: Invalid theme configuration, set to \"" - << itemMargin.x << " " << itemMargin.y << "\""; + LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" + << itemMargin.x << " " << itemMargin.y << "\""; } else { mFlexboxComponent.setItemMargin(itemMargin); @@ -229,7 +228,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, if (controllerPos.x < -1.0f || controllerPos.x > 2.0f || controllerPos.y < -1.0f || controllerPos.y > 2.0f) { LOG(LogWarning) - << "BadgesComponent: Invalid theme configuration, set to \"" + << "BadgeComponent: Invalid theme configuration, set to \"" << controllerPos.x << " " << controllerPos.y << "\""; } else { @@ -241,7 +240,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, const float controllerSize = elem->get("controllerSize"); if (controllerSize < 0.1f || controllerSize > 2.0f) { LOG(LogWarning) - << "BadgesComponent: Invalid theme configuration, set to \"" + << "BadgeComponent: Invalid theme configuration, set to \"" << controllerSize << "\""; } else { diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgeComponent.h similarity index 86% rename from es-core/src/components/BadgesComponent.h rename to es-core/src/components/BadgeComponent.h index a87c34e2b..2f998b9da 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgeComponent.h @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT // // EmulationStation Desktop Edition -// BadgesComponent.h +// BadgeComponent.h // // Game badges icons. // Used by the gamelist views. // -#ifndef ES_CORE_COMPONENTS_BADGES_COMPONENT_H -#define ES_CORE_COMPONENTS_BADGES_COMPONENT_H +#ifndef ES_CORE_COMPONENTS_BADGE_COMPONENT_H +#define ES_CORE_COMPONENTS_BADGE_COMPONENT_H #include "FlexboxComponent.h" #include "GuiComponent.h" @@ -19,10 +19,10 @@ struct GameControllers { std::string fileName; }; -class BadgesComponent : public GuiComponent +class BadgeComponent : public GuiComponent { public: - BadgesComponent(Window* window); + BadgeComponent(Window* window); struct BadgeInfo { std::string badgeType; @@ -60,4 +60,4 @@ private: std::map mBadgeIcons; }; -#endif // ES_CORE_COMPONENTS_BADGES_COMPONENT_H +#endif // ES_CORE_COMPONENTS_BADGE_COMPONENT_H From f2c787e7696cbb41e238cd21e60264794fe98cd8 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 20:49:00 +0200 Subject: [PATCH 099/128] Moved the resources/help directory to resources/graphics/help --- es-core/src/components/HelpComponent.cpp | 78 +++++++-------- es-core/src/guis/GuiInputConfig.cpp | 96 +++++++++---------- resources/{ => graphics}/help/button_1.svg | 0 resources/{ => graphics}/help/button_2.svg | 0 resources/{ => graphics}/help/button_3.svg | 0 resources/{ => graphics}/help/button_4.svg | 0 resources/{ => graphics}/help/button_a_PS.svg | 0 .../{ => graphics}/help/button_a_SNES.svg | 0 .../{ => graphics}/help/button_a_XBOX.svg | 0 resources/{ => graphics}/help/button_b_PS.svg | 0 .../{ => graphics}/help/button_b_SNES.svg | 0 .../{ => graphics}/help/button_b_XBOX.svg | 0 .../{ => graphics}/help/button_back_PS4.svg | 0 .../{ => graphics}/help/button_back_PS5.svg | 0 .../{ => graphics}/help/button_back_SNES.svg | 0 .../{ => graphics}/help/button_back_XBOX.svg | 0 .../help/button_back_XBOX360.svg | 0 .../{ => graphics}/help/button_hotkey.svg | 0 resources/{ => graphics}/help/button_l.svg | 0 resources/{ => graphics}/help/button_lr.svg | 0 resources/{ => graphics}/help/button_lt.svg | 0 resources/{ => graphics}/help/button_r.svg | 0 resources/{ => graphics}/help/button_rt.svg | 0 .../{ => graphics}/help/button_start_PS4.svg | 0 .../{ => graphics}/help/button_start_PS5.svg | 0 .../{ => graphics}/help/button_start_SNES.svg | 0 .../{ => graphics}/help/button_start_XBOX.svg | 0 .../help/button_start_XBOX360.svg | 0 resources/{ => graphics}/help/button_x_PS.svg | 0 .../{ => graphics}/help/button_x_SNES.svg | 0 .../{ => graphics}/help/button_x_XBOX.svg | 0 resources/{ => graphics}/help/button_y_PS.svg | 0 .../{ => graphics}/help/button_y_SNES.svg | 0 .../{ => graphics}/help/button_y_XBOX.svg | 0 resources/{ => graphics}/help/dpad_all.svg | 0 resources/{ => graphics}/help/dpad_down.svg | 0 resources/{ => graphics}/help/dpad_left.svg | 0 .../{ => graphics}/help/dpad_leftright.svg | 0 resources/{ => graphics}/help/dpad_right.svg | 0 resources/{ => graphics}/help/dpad_up.svg | 0 resources/{ => graphics}/help/dpad_updown.svg | 0 .../{ => graphics}/help/mbuttons_a_PS.svg | 0 .../{ => graphics}/help/mbuttons_a_SNES.svg | 0 .../{ => graphics}/help/mbuttons_a_XBOX.svg | 0 .../{ => graphics}/help/mbuttons_b_PS.svg | 0 .../{ => graphics}/help/mbuttons_b_SNES.svg | 0 .../{ => graphics}/help/mbuttons_b_XBOX.svg | 0 .../{ => graphics}/help/mbuttons_x_PS.svg | 0 .../{ => graphics}/help/mbuttons_x_SNES.svg | 0 .../{ => graphics}/help/mbuttons_x_XBOX.svg | 0 .../{ => graphics}/help/mbuttons_y_PS.svg | 0 .../{ => graphics}/help/mbuttons_y_SNES.svg | 0 .../{ => graphics}/help/mbuttons_y_XBOX.svg | 0 resources/{ => graphics}/help/thumbstick.svg | 0 .../{ => graphics}/help/thumbstick_click.svg | 0 .../{ => graphics}/help/thumbstick_down.svg | 0 .../{ => graphics}/help/thumbstick_left.svg | 0 .../{ => graphics}/help/thumbstick_right.svg | 0 .../{ => graphics}/help/thumbstick_up.svg | 0 59 files changed, 87 insertions(+), 87 deletions(-) rename resources/{ => graphics}/help/button_1.svg (100%) rename resources/{ => graphics}/help/button_2.svg (100%) rename resources/{ => graphics}/help/button_3.svg (100%) rename resources/{ => graphics}/help/button_4.svg (100%) rename resources/{ => graphics}/help/button_a_PS.svg (100%) rename resources/{ => graphics}/help/button_a_SNES.svg (100%) rename resources/{ => graphics}/help/button_a_XBOX.svg (100%) rename resources/{ => graphics}/help/button_b_PS.svg (100%) rename resources/{ => graphics}/help/button_b_SNES.svg (100%) rename resources/{ => graphics}/help/button_b_XBOX.svg (100%) rename resources/{ => graphics}/help/button_back_PS4.svg (100%) rename resources/{ => graphics}/help/button_back_PS5.svg (100%) rename resources/{ => graphics}/help/button_back_SNES.svg (100%) rename resources/{ => graphics}/help/button_back_XBOX.svg (100%) rename resources/{ => graphics}/help/button_back_XBOX360.svg (100%) rename resources/{ => graphics}/help/button_hotkey.svg (100%) rename resources/{ => graphics}/help/button_l.svg (100%) rename resources/{ => graphics}/help/button_lr.svg (100%) rename resources/{ => graphics}/help/button_lt.svg (100%) rename resources/{ => graphics}/help/button_r.svg (100%) rename resources/{ => graphics}/help/button_rt.svg (100%) rename resources/{ => graphics}/help/button_start_PS4.svg (100%) rename resources/{ => graphics}/help/button_start_PS5.svg (100%) rename resources/{ => graphics}/help/button_start_SNES.svg (100%) rename resources/{ => graphics}/help/button_start_XBOX.svg (100%) rename resources/{ => graphics}/help/button_start_XBOX360.svg (100%) rename resources/{ => graphics}/help/button_x_PS.svg (100%) rename resources/{ => graphics}/help/button_x_SNES.svg (100%) rename resources/{ => graphics}/help/button_x_XBOX.svg (100%) rename resources/{ => graphics}/help/button_y_PS.svg (100%) rename resources/{ => graphics}/help/button_y_SNES.svg (100%) rename resources/{ => graphics}/help/button_y_XBOX.svg (100%) rename resources/{ => graphics}/help/dpad_all.svg (100%) rename resources/{ => graphics}/help/dpad_down.svg (100%) rename resources/{ => graphics}/help/dpad_left.svg (100%) rename resources/{ => graphics}/help/dpad_leftright.svg (100%) rename resources/{ => graphics}/help/dpad_right.svg (100%) rename resources/{ => graphics}/help/dpad_up.svg (100%) rename resources/{ => graphics}/help/dpad_updown.svg (100%) rename resources/{ => graphics}/help/mbuttons_a_PS.svg (100%) rename resources/{ => graphics}/help/mbuttons_a_SNES.svg (100%) rename resources/{ => graphics}/help/mbuttons_a_XBOX.svg (100%) rename resources/{ => graphics}/help/mbuttons_b_PS.svg (100%) rename resources/{ => graphics}/help/mbuttons_b_SNES.svg (100%) rename resources/{ => graphics}/help/mbuttons_b_XBOX.svg (100%) rename resources/{ => graphics}/help/mbuttons_x_PS.svg (100%) rename resources/{ => graphics}/help/mbuttons_x_SNES.svg (100%) rename resources/{ => graphics}/help/mbuttons_x_XBOX.svg (100%) rename resources/{ => graphics}/help/mbuttons_y_PS.svg (100%) rename resources/{ => graphics}/help/mbuttons_y_SNES.svg (100%) rename resources/{ => graphics}/help/mbuttons_y_XBOX.svg (100%) rename resources/{ => graphics}/help/thumbstick.svg (100%) rename resources/{ => graphics}/help/thumbstick_click.svg (100%) rename resources/{ => graphics}/help/thumbstick_down.svg (100%) rename resources/{ => graphics}/help/thumbstick_left.svg (100%) rename resources/{ => graphics}/help/thumbstick_right.svg (100%) rename resources/{ => graphics}/help/thumbstick_up.svg (100%) diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index 7b6c7197f..49b68e354 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -34,129 +34,129 @@ void HelpComponent::assignIcons() // These graphics files are common between all controller types. sIconPathMap["up/down"] = mStyle.mCustomButtons.dpad_updown.empty() ? - ":/help/dpad_updown.svg" : + ":/graphics/help/dpad_updown.svg" : mStyle.mCustomButtons.dpad_updown; sIconPathMap["left/right"] = mStyle.mCustomButtons.dpad_leftright.empty() ? - ":/help/dpad_leftright.svg" : + ":/graphics/help/dpad_leftright.svg" : mStyle.mCustomButtons.dpad_leftright; sIconPathMap["up/down/left/right"] = mStyle.mCustomButtons.dpad_all.empty() ? - ":/help/dpad_all.svg" : + ":/graphics/help/dpad_all.svg" : mStyle.mCustomButtons.dpad_all; sIconPathMap["thumbstickclick"] = mStyle.mCustomButtons.thumbstick_click.empty() ? - ":/help/thumbstick_click.svg" : + ":/graphics/help/thumbstick_click.svg" : mStyle.mCustomButtons.thumbstick_click; - sIconPathMap["l"] = mStyle.mCustomButtons.button_l.empty() ? ":/help/button_l.svg" : + sIconPathMap["l"] = mStyle.mCustomButtons.button_l.empty() ? ":/graphics/help/button_l.svg" : mStyle.mCustomButtons.button_l; - sIconPathMap["r"] = mStyle.mCustomButtons.button_r.empty() ? ":/help/button_r.svg" : + sIconPathMap["r"] = mStyle.mCustomButtons.button_r.empty() ? ":/graphics/help/button_r.svg" : mStyle.mCustomButtons.button_r; - sIconPathMap["lr"] = mStyle.mCustomButtons.button_lr.empty() ? ":/help/button_lr.svg" : + sIconPathMap["lr"] = mStyle.mCustomButtons.button_lr.empty() ? ":/graphics/help/button_lr.svg" : mStyle.mCustomButtons.button_lr; - sIconPathMap["lt"] = mStyle.mCustomButtons.button_lt.empty() ? ":/help/button_lt.svg" : + sIconPathMap["lt"] = mStyle.mCustomButtons.button_lt.empty() ? ":/graphics/help/button_lt.svg" : mStyle.mCustomButtons.button_lt; - sIconPathMap["rt"] = mStyle.mCustomButtons.button_rt.empty() ? ":/help/button_rt.svg" : + sIconPathMap["rt"] = mStyle.mCustomButtons.button_rt.empty() ? ":/graphics/help/button_rt.svg" : mStyle.mCustomButtons.button_rt; // These graphics files are custom per controller type. if (controllerType == "snes") { sIconPathMap["a"] = mStyle.mCustomButtons.button_a_SNES.empty() ? - ":/help/button_a_SNES.svg" : + ":/graphics/help/button_a_SNES.svg" : mStyle.mCustomButtons.button_a_SNES; sIconPathMap["b"] = mStyle.mCustomButtons.button_b_SNES.empty() ? - ":/help/button_b_SNES.svg" : + ":/graphics/help/button_b_SNES.svg" : mStyle.mCustomButtons.button_b_SNES; sIconPathMap["x"] = mStyle.mCustomButtons.button_x_SNES.empty() ? - ":/help/button_x_SNES.svg" : + ":/graphics/help/button_x_SNES.svg" : mStyle.mCustomButtons.button_x_SNES; sIconPathMap["y"] = mStyle.mCustomButtons.button_y_SNES.empty() ? - ":/help/button_y_SNES.svg" : + ":/graphics/help/button_y_SNES.svg" : mStyle.mCustomButtons.button_y_SNES; sIconPathMap["start"] = mStyle.mCustomButtons.button_start_SNES.empty() ? - ":/help/button_start_SNES.svg" : + ":/graphics/help/button_start_SNES.svg" : mStyle.mCustomButtons.button_start_SNES; sIconPathMap["back"] = mStyle.mCustomButtons.button_back_SNES.empty() ? - ":/help/button_back_SNES.svg" : + ":/graphics/help/button_back_SNES.svg" : mStyle.mCustomButtons.button_back_SNES; } else if (controllerType == "ps4") { sIconPathMap["a"] = mStyle.mCustomButtons.button_a_PS.empty() ? - ":/help/button_a_PS.svg" : + ":/graphics/help/button_a_PS.svg" : mStyle.mCustomButtons.button_a_PS; sIconPathMap["b"] = mStyle.mCustomButtons.button_b_PS.empty() ? - ":/help/button_b_PS.svg" : + ":/graphics/help/button_b_PS.svg" : mStyle.mCustomButtons.button_b_PS; sIconPathMap["x"] = mStyle.mCustomButtons.button_x_PS.empty() ? - ":/help/button_x_PS.svg" : + ":/graphics/help/button_x_PS.svg" : mStyle.mCustomButtons.button_x_PS; sIconPathMap["y"] = mStyle.mCustomButtons.button_y_PS.empty() ? - ":/help/button_y_PS.svg" : + ":/graphics/help/button_y_PS.svg" : mStyle.mCustomButtons.button_y_PS; sIconPathMap["start"] = mStyle.mCustomButtons.button_start_PS4.empty() ? - ":/help/button_start_PS4.svg" : + ":/graphics/help/button_start_PS4.svg" : mStyle.mCustomButtons.button_start_PS4; sIconPathMap["back"] = mStyle.mCustomButtons.button_back_PS4.empty() ? - ":/help/button_back_PS4.svg" : + ":/graphics/help/button_back_PS4.svg" : mStyle.mCustomButtons.button_back_PS4; } else if (controllerType == "ps5") { sIconPathMap["a"] = mStyle.mCustomButtons.button_a_PS.empty() ? - ":/help/button_a_PS.svg" : + ":/graphics/help/button_a_PS.svg" : mStyle.mCustomButtons.button_a_PS; sIconPathMap["b"] = mStyle.mCustomButtons.button_b_PS.empty() ? - ":/help/button_b_PS.svg" : + ":/graphics/help/button_b_PS.svg" : mStyle.mCustomButtons.button_b_PS; sIconPathMap["x"] = mStyle.mCustomButtons.button_x_PS.empty() ? - ":/help/button_x_PS.svg" : + ":/graphics/help/button_x_PS.svg" : mStyle.mCustomButtons.button_x_PS; sIconPathMap["y"] = mStyle.mCustomButtons.button_y_PS.empty() ? - ":/help/button_y_PS.svg" : + ":/graphics/help/button_y_PS.svg" : mStyle.mCustomButtons.button_y_PS; sIconPathMap["start"] = mStyle.mCustomButtons.button_start_PS5.empty() ? - ":/help/button_start_PS5.svg" : + ":/graphics/help/button_start_PS5.svg" : mStyle.mCustomButtons.button_start_PS5; sIconPathMap["back"] = mStyle.mCustomButtons.button_back_PS5.empty() ? - ":/help/button_back_PS5.svg" : + ":/graphics/help/button_back_PS5.svg" : mStyle.mCustomButtons.button_back_PS5; } else if (controllerType == "xbox360") { sIconPathMap["a"] = mStyle.mCustomButtons.button_a_XBOX.empty() ? - ":/help/button_a_XBOX.svg" : + ":/graphics/help/button_a_XBOX.svg" : mStyle.mCustomButtons.button_a_XBOX; sIconPathMap["b"] = mStyle.mCustomButtons.button_b_XBOX.empty() ? - ":/help/button_b_XBOX.svg" : + ":/graphics/help/button_b_XBOX.svg" : mStyle.mCustomButtons.button_b_XBOX; sIconPathMap["x"] = mStyle.mCustomButtons.button_x_XBOX.empty() ? - ":/help/button_x_XBOX.svg" : + ":/graphics/help/button_x_XBOX.svg" : mStyle.mCustomButtons.button_x_XBOX; sIconPathMap["y"] = mStyle.mCustomButtons.button_y_XBOX.empty() ? - ":/help/button_y_XBOX.svg" : + ":/graphics/help/button_y_XBOX.svg" : mStyle.mCustomButtons.button_y_XBOX; sIconPathMap["start"] = mStyle.mCustomButtons.button_start_XBOX360.empty() ? - ":/help/button_start_XBOX360.svg" : + ":/graphics/help/button_start_XBOX360.svg" : mStyle.mCustomButtons.button_start_XBOX360; sIconPathMap["back"] = mStyle.mCustomButtons.button_back_XBOX360.empty() ? - ":/help/button_back_XBOX360.svg" : + ":/graphics/help/button_back_XBOX360.svg" : mStyle.mCustomButtons.button_back_XBOX360; } else { // Xbox One and later. sIconPathMap["a"] = mStyle.mCustomButtons.button_a_XBOX.empty() ? - ":/help/button_a_XBOX.svg" : + ":/graphics/help/button_a_XBOX.svg" : mStyle.mCustomButtons.button_a_XBOX; sIconPathMap["b"] = mStyle.mCustomButtons.button_b_XBOX.empty() ? - ":/help/button_b_XBOX.svg" : + ":/graphics/help/button_b_XBOX.svg" : mStyle.mCustomButtons.button_b_XBOX; sIconPathMap["x"] = mStyle.mCustomButtons.button_x_XBOX.empty() ? - ":/help/button_x_XBOX.svg" : + ":/graphics/help/button_x_XBOX.svg" : mStyle.mCustomButtons.button_x_XBOX; sIconPathMap["y"] = mStyle.mCustomButtons.button_y_XBOX.empty() ? - ":/help/button_y_XBOX.svg" : + ":/graphics/help/button_y_XBOX.svg" : mStyle.mCustomButtons.button_y_XBOX; sIconPathMap["start"] = mStyle.mCustomButtons.button_start_XBOX.empty() ? - ":/help/button_start_XBOX.svg" : + ":/graphics/help/button_start_XBOX.svg" : mStyle.mCustomButtons.button_start_XBOX; sIconPathMap["back"] = mStyle.mCustomButtons.button_back_XBOX.empty() ? - ":/help/button_back_XBOX.svg" : + ":/graphics/help/button_back_XBOX.svg" : mStyle.mCustomButtons.button_back_XBOX; } diff --git a/es-core/src/guis/GuiInputConfig.cpp b/es-core/src/guis/GuiInputConfig.cpp index 516d17c6e..f9bf3d10f 100644 --- a/es-core/src/guis/GuiInputConfig.cpp +++ b/es-core/src/guis/GuiInputConfig.cpp @@ -201,67 +201,67 @@ void GuiInputConfig::populateConfigList() std::string controllerType = Settings::getInstance()->getString("InputControllerType"); // clang-format off - sGuiInputConfigList[0] = {"Up", false, "D-PAD UP", ":/help/dpad_up.svg"}; - sGuiInputConfigList[1] = {"Down", false, "D-PAD DOWN", ":/help/dpad_down.svg"}; - sGuiInputConfigList[2] = {"Left", false, "D-PAD LEFT", ":/help/dpad_left.svg"}; - sGuiInputConfigList[3] = {"Right", false, "D-PAD RIGHT", ":/help/dpad_right.svg"}; + sGuiInputConfigList[0] = {"Up", false, "D-PAD UP", ":/graphics/help/dpad_up.svg"}; + sGuiInputConfigList[1] = {"Down", false, "D-PAD DOWN", ":/graphics/help/dpad_down.svg"}; + sGuiInputConfigList[2] = {"Left", false, "D-PAD LEFT", ":/graphics/help/dpad_left.svg"}; + sGuiInputConfigList[3] = {"Right", false, "D-PAD RIGHT", ":/graphics/help/dpad_right.svg"}; if (controllerType == "snes") { - sGuiInputConfigList[4] = {"Back", false, "SELECT", ":/help/button_back_SNES.svg"}; - sGuiInputConfigList[5] = {"Start", false, "START", ":/help/button_start_SNES.svg"}; - sGuiInputConfigList[6] = {"A", false, "B", ":/help/mbuttons_a_SNES.svg"}; - sGuiInputConfigList[7] = {"B", false, "A", ":/help/mbuttons_b_SNES.svg"}; - sGuiInputConfigList[8] = {"X", true, "Y", ":/help/mbuttons_x_SNES.svg"}; - sGuiInputConfigList[9] = {"Y", true, "X", ":/help/mbuttons_y_SNES.svg"}; + sGuiInputConfigList[4] = {"Back", false, "SELECT", ":/graphics/help/button_back_SNES.svg"}; + sGuiInputConfigList[5] = {"Start", false, "START", ":/graphics/help/button_start_SNES.svg"}; + sGuiInputConfigList[6] = {"A", false, "B", ":/graphics/help/mbuttons_a_SNES.svg"}; + sGuiInputConfigList[7] = {"B", false, "A", ":/graphics/help/mbuttons_b_SNES.svg"}; + sGuiInputConfigList[8] = {"X", true, "Y", ":/graphics/help/mbuttons_x_SNES.svg"}; + sGuiInputConfigList[9] = {"Y", true, "X", ":/graphics/help/mbuttons_y_SNES.svg"}; } else if (controllerType == "ps4") { - sGuiInputConfigList[4] = {"Back", false, "SHARE", ":/help/button_back_PS4.svg"}; - sGuiInputConfigList[5] = {"Start", false, "OPTIONS", ":/help/button_start_PS4.svg"}; - sGuiInputConfigList[6] = {"A", false, "CROSS", ":/help/mbuttons_a_PS.svg"}; - sGuiInputConfigList[7] = {"B", false, "CIRCLE", ":/help/mbuttons_b_PS.svg"}; - sGuiInputConfigList[8] = {"X", true, "SQUARE", ":/help/mbuttons_x_PS.svg"}; - sGuiInputConfigList[9] = {"Y", true, "TRIANGLE", ":/help/mbuttons_y_PS.svg"}; + sGuiInputConfigList[4] = {"Back", false, "SHARE", ":/graphics/help/button_back_PS4.svg"}; + sGuiInputConfigList[5] = {"Start", false, "OPTIONS", ":/graphics/help/button_start_PS4.svg"}; + sGuiInputConfigList[6] = {"A", false, "CROSS", ":/graphics/help/mbuttons_a_PS.svg"}; + sGuiInputConfigList[7] = {"B", false, "CIRCLE", ":/graphics/help/mbuttons_b_PS.svg"}; + sGuiInputConfigList[8] = {"X", true, "SQUARE", ":/graphics/help/mbuttons_x_PS.svg"}; + sGuiInputConfigList[9] = {"Y", true, "TRIANGLE", ":/graphics/help/mbuttons_y_PS.svg"}; } else if (controllerType == "ps5") { - sGuiInputConfigList[4] = {"Back", false, "CREATE", ":/help/button_back_PS5.svg"}; - sGuiInputConfigList[5] = {"Start", false, "OPTIONS", ":/help/button_start_PS5.svg"}; - sGuiInputConfigList[6] = {"A", false, "CROSS", ":/help/mbuttons_a_PS.svg"}; - sGuiInputConfigList[7] = {"B", false, "CIRCLE", ":/help/mbuttons_b_PS.svg"}; - sGuiInputConfigList[8] = {"X", true, "SQUARE", ":/help/mbuttons_x_PS.svg"}; - sGuiInputConfigList[9] = {"Y", true, "TRIANGLE", ":/help/mbuttons_y_PS.svg"}; + sGuiInputConfigList[4] = {"Back", false, "CREATE", ":/graphics/help/button_back_PS5.svg"}; + sGuiInputConfigList[5] = {"Start", false, "OPTIONS", ":/graphics/help/button_start_PS5.svg"}; + sGuiInputConfigList[6] = {"A", false, "CROSS", ":/graphics/help/mbuttons_a_PS.svg"}; + sGuiInputConfigList[7] = {"B", false, "CIRCLE", ":/graphics/help/mbuttons_b_PS.svg"}; + sGuiInputConfigList[8] = {"X", true, "SQUARE", ":/graphics/help/mbuttons_x_PS.svg"}; + sGuiInputConfigList[9] = {"Y", true, "TRIANGLE", ":/graphics/help/mbuttons_y_PS.svg"}; } else if (controllerType == "xbox360") { - sGuiInputConfigList[4] = {"Back", false, "BACK", ":/help/button_back_XBOX360.svg"}; - sGuiInputConfigList[5] = {"Start", false, "START", ":/help/button_start_XBOX360.svg"}; - sGuiInputConfigList[6] = {"A", false, "A", ":/help/mbuttons_a_XBOX.svg"}; - sGuiInputConfigList[7] = {"B", false, "B", ":/help/mbuttons_b_XBOX.svg"}; - sGuiInputConfigList[8] = {"X", true, "X", ":/help/mbuttons_x_XBOX.svg"}; - sGuiInputConfigList[9] = {"Y", true, "Y", ":/help/mbuttons_y_XBOX.svg"}; + sGuiInputConfigList[4] = {"Back", false, "BACK", ":/graphics/help/button_back_XBOX360.svg"}; + sGuiInputConfigList[5] = {"Start", false, "START", ":/graphics/help/button_start_XBOX360.svg"}; + sGuiInputConfigList[6] = {"A", false, "A", ":/graphics/help/mbuttons_a_XBOX.svg"}; + sGuiInputConfigList[7] = {"B", false, "B", ":/graphics/help/mbuttons_b_XBOX.svg"}; + sGuiInputConfigList[8] = {"X", true, "X", ":/graphics/help/mbuttons_x_XBOX.svg"}; + sGuiInputConfigList[9] = {"Y", true, "Y", ":/graphics/help/mbuttons_y_XBOX.svg"}; } else { // Xbox One and later. - sGuiInputConfigList[4] = {"Back", false, "VIEW", ":/help/button_back_XBOX.svg"}; - sGuiInputConfigList[5] = {"Start", false, "MENU", ":/help/button_start_XBOX.svg"}; - sGuiInputConfigList[6] = {"A", false, "A", ":/help/mbuttons_a_XBOX.svg"}; - sGuiInputConfigList[7] = {"B", false, "B", ":/help/mbuttons_b_XBOX.svg"}; - sGuiInputConfigList[8] = {"X", true, "X", ":/help/mbuttons_x_XBOX.svg"}; - sGuiInputConfigList[9] = {"Y", true, "Y", ":/help/mbuttons_y_XBOX.svg"}; + sGuiInputConfigList[4] = {"Back", false, "VIEW", ":/graphics/help/button_back_XBOX.svg"}; + sGuiInputConfigList[5] = {"Start", false, "MENU", ":/graphics/help/button_start_XBOX.svg"}; + sGuiInputConfigList[6] = {"A", false, "A", ":/graphics/help/mbuttons_a_XBOX.svg"}; + sGuiInputConfigList[7] = {"B", false, "B", ":/graphics/help/mbuttons_b_XBOX.svg"}; + sGuiInputConfigList[8] = {"X", true, "X", ":/graphics/help/mbuttons_x_XBOX.svg"}; + sGuiInputConfigList[9] = {"Y", true, "Y", ":/graphics/help/mbuttons_y_XBOX.svg"}; } - sGuiInputConfigList[10] = {"LeftShoulder", true, "LEFT SHOULDER", ":/help/button_l.svg"}; - sGuiInputConfigList[11] = {"RightShoulder", true, "RIGHT SHOULDER", ":/help/button_r.svg"}; - sGuiInputConfigList[12] = {"LeftTrigger", true, "LEFT TRIGGER", ":/help/button_lt.svg"}; - sGuiInputConfigList[13] = {"RightTrigger", true, "RIGHT TRIGGER", ":/help/button_rt.svg"}; - sGuiInputConfigList[14] = {"LeftThumbstickUp", true, "LEFT THUMBSTICK UP", ":/help/thumbstick_up.svg"}; - sGuiInputConfigList[15] = {"LeftThumbstickDown", true, "LEFT THUMBSTICK DOWN", ":/help/thumbstick_down.svg"}; - sGuiInputConfigList[16] = {"LeftThumbstickLeft", true, "LEFT THUMBSTICK LEFT", ":/help/thumbstick_left.svg"}; - sGuiInputConfigList[17] = {"LeftThumbstickRight", true, "LEFT THUMBSTICK RIGHT", ":/help/thumbstick_right.svg"}; - sGuiInputConfigList[18] = {"LeftThumbstickClick", true, "LEFT THUMBSTICK CLICK", ":/help/thumbstick_click.svg"}; - sGuiInputConfigList[19] = {"RightThumbstickUp", true, "RIGHT THUMBSTICK UP", ":/help/thumbstick_up.svg"}; - sGuiInputConfigList[20] = {"RightThumbstickDown", true, "RIGHT THUMBSTICK DOWN", ":/help/thumbstick_down.svg"}; - sGuiInputConfigList[21] = {"RightThumbstickLeft", true, "RIGHT THUMBSTICK LEFT", ":/help/thumbstick_left.svg"}; - sGuiInputConfigList[22] = {"RightThumbstickRight", true, "RIGHT THUMBSTICK RIGHT", ":/help/thumbstick_right.svg"}; - sGuiInputConfigList[23] = {"RightThumbstickClick", true, "RIGHT THUMBSTICK CLICK", ":/help/thumbstick_click.svg"}; + sGuiInputConfigList[10] = {"LeftShoulder", true, "LEFT SHOULDER", ":/graphics/help/button_l.svg"}; + sGuiInputConfigList[11] = {"RightShoulder", true, "RIGHT SHOULDER", ":/graphics/help/button_r.svg"}; + sGuiInputConfigList[12] = {"LeftTrigger", true, "LEFT TRIGGER", ":/graphics/help/button_lt.svg"}; + sGuiInputConfigList[13] = {"RightTrigger", true, "RIGHT TRIGGER", ":/graphics/help/button_rt.svg"}; + sGuiInputConfigList[14] = {"LeftThumbstickUp", true, "LEFT THUMBSTICK UP", ":/graphics/help/thumbstick_up.svg"}; + sGuiInputConfigList[15] = {"LeftThumbstickDown", true, "LEFT THUMBSTICK DOWN", ":/graphics/help/thumbstick_down.svg"}; + sGuiInputConfigList[16] = {"LeftThumbstickLeft", true, "LEFT THUMBSTICK LEFT", ":/graphics/help/thumbstick_left.svg"}; + sGuiInputConfigList[17] = {"LeftThumbstickRight", true, "LEFT THUMBSTICK RIGHT", ":/graphics/help/thumbstick_right.svg"}; + sGuiInputConfigList[18] = {"LeftThumbstickClick", true, "LEFT THUMBSTICK CLICK", ":/graphics/help/thumbstick_click.svg"}; + sGuiInputConfigList[19] = {"RightThumbstickUp", true, "RIGHT THUMBSTICK UP", ":/graphics/help/thumbstick_up.svg"}; + sGuiInputConfigList[20] = {"RightThumbstickDown", true, "RIGHT THUMBSTICK DOWN", ":/graphics/help/thumbstick_down.svg"}; + sGuiInputConfigList[21] = {"RightThumbstickLeft", true, "RIGHT THUMBSTICK LEFT", ":/graphics/help/thumbstick_left.svg"}; + sGuiInputConfigList[22] = {"RightThumbstickRight", true, "RIGHT THUMBSTICK RIGHT", ":/graphics/help/thumbstick_right.svg"}; + sGuiInputConfigList[23] = {"RightThumbstickClick", true, "RIGHT THUMBSTICK CLICK", ":/graphics/help/thumbstick_click.svg"}; // clang-format on } diff --git a/resources/help/button_1.svg b/resources/graphics/help/button_1.svg similarity index 100% rename from resources/help/button_1.svg rename to resources/graphics/help/button_1.svg diff --git a/resources/help/button_2.svg b/resources/graphics/help/button_2.svg similarity index 100% rename from resources/help/button_2.svg rename to resources/graphics/help/button_2.svg diff --git a/resources/help/button_3.svg b/resources/graphics/help/button_3.svg similarity index 100% rename from resources/help/button_3.svg rename to resources/graphics/help/button_3.svg diff --git a/resources/help/button_4.svg b/resources/graphics/help/button_4.svg similarity index 100% rename from resources/help/button_4.svg rename to resources/graphics/help/button_4.svg diff --git a/resources/help/button_a_PS.svg b/resources/graphics/help/button_a_PS.svg similarity index 100% rename from resources/help/button_a_PS.svg rename to resources/graphics/help/button_a_PS.svg diff --git a/resources/help/button_a_SNES.svg b/resources/graphics/help/button_a_SNES.svg similarity index 100% rename from resources/help/button_a_SNES.svg rename to resources/graphics/help/button_a_SNES.svg diff --git a/resources/help/button_a_XBOX.svg b/resources/graphics/help/button_a_XBOX.svg similarity index 100% rename from resources/help/button_a_XBOX.svg rename to resources/graphics/help/button_a_XBOX.svg diff --git a/resources/help/button_b_PS.svg b/resources/graphics/help/button_b_PS.svg similarity index 100% rename from resources/help/button_b_PS.svg rename to resources/graphics/help/button_b_PS.svg diff --git a/resources/help/button_b_SNES.svg b/resources/graphics/help/button_b_SNES.svg similarity index 100% rename from resources/help/button_b_SNES.svg rename to resources/graphics/help/button_b_SNES.svg diff --git a/resources/help/button_b_XBOX.svg b/resources/graphics/help/button_b_XBOX.svg similarity index 100% rename from resources/help/button_b_XBOX.svg rename to resources/graphics/help/button_b_XBOX.svg diff --git a/resources/help/button_back_PS4.svg b/resources/graphics/help/button_back_PS4.svg similarity index 100% rename from resources/help/button_back_PS4.svg rename to resources/graphics/help/button_back_PS4.svg diff --git a/resources/help/button_back_PS5.svg b/resources/graphics/help/button_back_PS5.svg similarity index 100% rename from resources/help/button_back_PS5.svg rename to resources/graphics/help/button_back_PS5.svg diff --git a/resources/help/button_back_SNES.svg b/resources/graphics/help/button_back_SNES.svg similarity index 100% rename from resources/help/button_back_SNES.svg rename to resources/graphics/help/button_back_SNES.svg diff --git a/resources/help/button_back_XBOX.svg b/resources/graphics/help/button_back_XBOX.svg similarity index 100% rename from resources/help/button_back_XBOX.svg rename to resources/graphics/help/button_back_XBOX.svg diff --git a/resources/help/button_back_XBOX360.svg b/resources/graphics/help/button_back_XBOX360.svg similarity index 100% rename from resources/help/button_back_XBOX360.svg rename to resources/graphics/help/button_back_XBOX360.svg diff --git a/resources/help/button_hotkey.svg b/resources/graphics/help/button_hotkey.svg similarity index 100% rename from resources/help/button_hotkey.svg rename to resources/graphics/help/button_hotkey.svg diff --git a/resources/help/button_l.svg b/resources/graphics/help/button_l.svg similarity index 100% rename from resources/help/button_l.svg rename to resources/graphics/help/button_l.svg diff --git a/resources/help/button_lr.svg b/resources/graphics/help/button_lr.svg similarity index 100% rename from resources/help/button_lr.svg rename to resources/graphics/help/button_lr.svg diff --git a/resources/help/button_lt.svg b/resources/graphics/help/button_lt.svg similarity index 100% rename from resources/help/button_lt.svg rename to resources/graphics/help/button_lt.svg diff --git a/resources/help/button_r.svg b/resources/graphics/help/button_r.svg similarity index 100% rename from resources/help/button_r.svg rename to resources/graphics/help/button_r.svg diff --git a/resources/help/button_rt.svg b/resources/graphics/help/button_rt.svg similarity index 100% rename from resources/help/button_rt.svg rename to resources/graphics/help/button_rt.svg diff --git a/resources/help/button_start_PS4.svg b/resources/graphics/help/button_start_PS4.svg similarity index 100% rename from resources/help/button_start_PS4.svg rename to resources/graphics/help/button_start_PS4.svg diff --git a/resources/help/button_start_PS5.svg b/resources/graphics/help/button_start_PS5.svg similarity index 100% rename from resources/help/button_start_PS5.svg rename to resources/graphics/help/button_start_PS5.svg diff --git a/resources/help/button_start_SNES.svg b/resources/graphics/help/button_start_SNES.svg similarity index 100% rename from resources/help/button_start_SNES.svg rename to resources/graphics/help/button_start_SNES.svg diff --git a/resources/help/button_start_XBOX.svg b/resources/graphics/help/button_start_XBOX.svg similarity index 100% rename from resources/help/button_start_XBOX.svg rename to resources/graphics/help/button_start_XBOX.svg diff --git a/resources/help/button_start_XBOX360.svg b/resources/graphics/help/button_start_XBOX360.svg similarity index 100% rename from resources/help/button_start_XBOX360.svg rename to resources/graphics/help/button_start_XBOX360.svg diff --git a/resources/help/button_x_PS.svg b/resources/graphics/help/button_x_PS.svg similarity index 100% rename from resources/help/button_x_PS.svg rename to resources/graphics/help/button_x_PS.svg diff --git a/resources/help/button_x_SNES.svg b/resources/graphics/help/button_x_SNES.svg similarity index 100% rename from resources/help/button_x_SNES.svg rename to resources/graphics/help/button_x_SNES.svg diff --git a/resources/help/button_x_XBOX.svg b/resources/graphics/help/button_x_XBOX.svg similarity index 100% rename from resources/help/button_x_XBOX.svg rename to resources/graphics/help/button_x_XBOX.svg diff --git a/resources/help/button_y_PS.svg b/resources/graphics/help/button_y_PS.svg similarity index 100% rename from resources/help/button_y_PS.svg rename to resources/graphics/help/button_y_PS.svg diff --git a/resources/help/button_y_SNES.svg b/resources/graphics/help/button_y_SNES.svg similarity index 100% rename from resources/help/button_y_SNES.svg rename to resources/graphics/help/button_y_SNES.svg diff --git a/resources/help/button_y_XBOX.svg b/resources/graphics/help/button_y_XBOX.svg similarity index 100% rename from resources/help/button_y_XBOX.svg rename to resources/graphics/help/button_y_XBOX.svg diff --git a/resources/help/dpad_all.svg b/resources/graphics/help/dpad_all.svg similarity index 100% rename from resources/help/dpad_all.svg rename to resources/graphics/help/dpad_all.svg diff --git a/resources/help/dpad_down.svg b/resources/graphics/help/dpad_down.svg similarity index 100% rename from resources/help/dpad_down.svg rename to resources/graphics/help/dpad_down.svg diff --git a/resources/help/dpad_left.svg b/resources/graphics/help/dpad_left.svg similarity index 100% rename from resources/help/dpad_left.svg rename to resources/graphics/help/dpad_left.svg diff --git a/resources/help/dpad_leftright.svg b/resources/graphics/help/dpad_leftright.svg similarity index 100% rename from resources/help/dpad_leftright.svg rename to resources/graphics/help/dpad_leftright.svg diff --git a/resources/help/dpad_right.svg b/resources/graphics/help/dpad_right.svg similarity index 100% rename from resources/help/dpad_right.svg rename to resources/graphics/help/dpad_right.svg diff --git a/resources/help/dpad_up.svg b/resources/graphics/help/dpad_up.svg similarity index 100% rename from resources/help/dpad_up.svg rename to resources/graphics/help/dpad_up.svg diff --git a/resources/help/dpad_updown.svg b/resources/graphics/help/dpad_updown.svg similarity index 100% rename from resources/help/dpad_updown.svg rename to resources/graphics/help/dpad_updown.svg diff --git a/resources/help/mbuttons_a_PS.svg b/resources/graphics/help/mbuttons_a_PS.svg similarity index 100% rename from resources/help/mbuttons_a_PS.svg rename to resources/graphics/help/mbuttons_a_PS.svg diff --git a/resources/help/mbuttons_a_SNES.svg b/resources/graphics/help/mbuttons_a_SNES.svg similarity index 100% rename from resources/help/mbuttons_a_SNES.svg rename to resources/graphics/help/mbuttons_a_SNES.svg diff --git a/resources/help/mbuttons_a_XBOX.svg b/resources/graphics/help/mbuttons_a_XBOX.svg similarity index 100% rename from resources/help/mbuttons_a_XBOX.svg rename to resources/graphics/help/mbuttons_a_XBOX.svg diff --git a/resources/help/mbuttons_b_PS.svg b/resources/graphics/help/mbuttons_b_PS.svg similarity index 100% rename from resources/help/mbuttons_b_PS.svg rename to resources/graphics/help/mbuttons_b_PS.svg diff --git a/resources/help/mbuttons_b_SNES.svg b/resources/graphics/help/mbuttons_b_SNES.svg similarity index 100% rename from resources/help/mbuttons_b_SNES.svg rename to resources/graphics/help/mbuttons_b_SNES.svg diff --git a/resources/help/mbuttons_b_XBOX.svg b/resources/graphics/help/mbuttons_b_XBOX.svg similarity index 100% rename from resources/help/mbuttons_b_XBOX.svg rename to resources/graphics/help/mbuttons_b_XBOX.svg diff --git a/resources/help/mbuttons_x_PS.svg b/resources/graphics/help/mbuttons_x_PS.svg similarity index 100% rename from resources/help/mbuttons_x_PS.svg rename to resources/graphics/help/mbuttons_x_PS.svg diff --git a/resources/help/mbuttons_x_SNES.svg b/resources/graphics/help/mbuttons_x_SNES.svg similarity index 100% rename from resources/help/mbuttons_x_SNES.svg rename to resources/graphics/help/mbuttons_x_SNES.svg diff --git a/resources/help/mbuttons_x_XBOX.svg b/resources/graphics/help/mbuttons_x_XBOX.svg similarity index 100% rename from resources/help/mbuttons_x_XBOX.svg rename to resources/graphics/help/mbuttons_x_XBOX.svg diff --git a/resources/help/mbuttons_y_PS.svg b/resources/graphics/help/mbuttons_y_PS.svg similarity index 100% rename from resources/help/mbuttons_y_PS.svg rename to resources/graphics/help/mbuttons_y_PS.svg diff --git a/resources/help/mbuttons_y_SNES.svg b/resources/graphics/help/mbuttons_y_SNES.svg similarity index 100% rename from resources/help/mbuttons_y_SNES.svg rename to resources/graphics/help/mbuttons_y_SNES.svg diff --git a/resources/help/mbuttons_y_XBOX.svg b/resources/graphics/help/mbuttons_y_XBOX.svg similarity index 100% rename from resources/help/mbuttons_y_XBOX.svg rename to resources/graphics/help/mbuttons_y_XBOX.svg diff --git a/resources/help/thumbstick.svg b/resources/graphics/help/thumbstick.svg similarity index 100% rename from resources/help/thumbstick.svg rename to resources/graphics/help/thumbstick.svg diff --git a/resources/help/thumbstick_click.svg b/resources/graphics/help/thumbstick_click.svg similarity index 100% rename from resources/help/thumbstick_click.svg rename to resources/graphics/help/thumbstick_click.svg diff --git a/resources/help/thumbstick_down.svg b/resources/graphics/help/thumbstick_down.svg similarity index 100% rename from resources/help/thumbstick_down.svg rename to resources/graphics/help/thumbstick_down.svg diff --git a/resources/help/thumbstick_left.svg b/resources/graphics/help/thumbstick_left.svg similarity index 100% rename from resources/help/thumbstick_left.svg rename to resources/graphics/help/thumbstick_left.svg diff --git a/resources/help/thumbstick_right.svg b/resources/graphics/help/thumbstick_right.svg similarity index 100% rename from resources/help/thumbstick_right.svg rename to resources/graphics/help/thumbstick_right.svg diff --git a/resources/help/thumbstick_up.svg b/resources/graphics/help/thumbstick_up.svg similarity index 100% rename from resources/help/thumbstick_up.svg rename to resources/graphics/help/thumbstick_up.svg From f9fb0ae2875b936e4a2b784135afc70b99c5aac5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 20:53:25 +0200 Subject: [PATCH 100/128] Removed two unused graphics files. --- resources/graphics/fav_add.svg | 5 ----- resources/graphics/fav_remove.svg | 4 ---- 2 files changed, 9 deletions(-) delete mode 100644 resources/graphics/fav_add.svg delete mode 100644 resources/graphics/fav_remove.svg diff --git a/resources/graphics/fav_add.svg b/resources/graphics/fav_add.svg deleted file mode 100644 index 34402f462..000000000 --- a/resources/graphics/fav_add.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/resources/graphics/fav_remove.svg b/resources/graphics/fav_remove.svg deleted file mode 100644 index bb5f3f2a7..000000000 --- a/resources/graphics/fav_remove.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - From 5c4d0821e290f5c4d16cb28bed7ebc8451ab6bc5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 23 Oct 2021 22:49:34 +0200 Subject: [PATCH 101/128] Renamed 'Controller' to 'Controller badge' in the metadata editor. --- es-app/src/MetaData.cpp | 4 ++-- es-app/src/guis/GuiMetaDataEd.cpp | 11 +++++------ es-app/src/guis/GuiMetaDataEd.h | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 943099fcb..1dc7a10e1 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -37,7 +37,7 @@ MetaDataDecl gameDecls[] = { {"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, {"playcount", MD_INT, "0", false, "times played", "enter number of times played", false}, -{"controller", MD_CONTROLLER, "", false, "controller", "select controller", false}, +{"controller", MD_CONTROLLER, "", false, "controller badge", "select controller badge", false}, {"altemulator", MD_ALT_EMULATOR, "", false, "alternative emulator", "select alternative emulator", false}, {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; @@ -58,7 +58,7 @@ MetaDataDecl folderDecls[] = { {"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, {"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, -{"controller", MD_CONTROLLER, "", false, "controller", "select controller", false}, +{"controller", MD_CONTROLLER, "", false, "controller badge", "select controller badge", false}, {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} }; // clang-format on diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index e46c1bb62..02d713c42 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -53,11 +53,11 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, , mMediaFilesUpdated{false} , mInvalidEmulatorEntry{false} { - mGameControllers = BadgeComponent::getGameControllers(); + mControllerBadges = BadgeComponent::getGameControllers(); // Remove the last "unknown" controller entry. - if (mGameControllers.size() > 1) - mGameControllers.pop_back(); + if (mControllerBadges.size() > 1) + mControllerBadges.pop_back(); addChild(&mBackground); addChild(&mGrid); @@ -206,11 +206,10 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, ed->setColor(TEXTCOLOR_USERMARKED); }; - row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal, - originalValue] { + row.makeAcceptInputHandler([this, title, ed, updateVal] { GuiSettings* s = new GuiSettings(mWindow, title); - for (auto controller : mGameControllers) { + for (auto controller : mControllerBadges) { std::string selectedLabel = ed->getValue(); std::string label; ComponentListRow row; diff --git a/es-app/src/guis/GuiMetaDataEd.h b/es-app/src/guis/GuiMetaDataEd.h index a4833c257..ec4ca13af 100644 --- a/es-app/src/guis/GuiMetaDataEd.h +++ b/es-app/src/guis/GuiMetaDataEd.h @@ -61,7 +61,7 @@ private: ScraperSearchParams mScraperParams; - std::vector mGameControllers; + std::vector mControllerBadges; std::vector> mEditors; std::vector mMetaDataDecl; From 5247a9f5fe6ce0ad95986a09ae65597bdcb190c7 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 12:10:38 +0200 Subject: [PATCH 102/128] Fixed an issue where the wrong scroll indicator could be shown. --- es-core/src/components/ComponentList.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index 1457e3d14..cee1ab3bb 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -249,8 +249,16 @@ void ComponentList::updateCameraOffset() while (mCameraOffset < target && i < mEntries.size()) { mCameraOffset += getRowHeight(mEntries.at(i).data); if (mCameraOffset > totalHeight - mSize.y) { - if (mSetupCompleted && mCameraOffset != oldCameraOffset) - mBottomCameraOffset = true; + if (mSetupCompleted) { + if (mScrollIndicatorStatus == ComponentList::SCROLL_NONE && + oldCameraOffset == 0.0f) + break; + if (mScrollIndicatorStatus != ComponentList::SCROLL_NONE && + oldCameraOffset == 0.0f) + mBottomCameraOffset = true; + else if (mCameraOffset != oldCameraOffset) + mBottomCameraOffset = true; + } break; } i++; From 0cf52c2d7172a8691038e471d31e368d972640de Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 13:05:44 +0200 Subject: [PATCH 103/128] Made it possible to filter the 'Controller badge' metadata field. --- es-app/src/FileFilterIndex.cpp | 44 ++++++++++++++++++++------- es-app/src/FileFilterIndex.h | 5 +++ es-app/src/guis/GuiGamelistFilter.cpp | 19 ++++++++++-- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/es-app/src/FileFilterIndex.cpp b/es-app/src/FileFilterIndex.cpp index 20063dea8..ec4384c18 100644 --- a/es-app/src/FileFilterIndex.cpp +++ b/es-app/src/FileFilterIndex.cpp @@ -33,6 +33,7 @@ FileFilterIndex::FileFilterIndex() , mFilterByKidGame(false) , mFilterByHidden(false) , mFilterByBroken(false) + , mFilterByController(false) , mFilterByAltemulator(false) { clearAllFilters(); @@ -50,6 +51,7 @@ FileFilterIndex::FileFilterIndex() {KIDGAME_FILTER, &mKidGameIndexAllKeys, &mFilterByKidGame, &mKidGameIndexFilteredKeys, "kidgame", false, "", "KIDGAME"}, {HIDDEN_FILTER, &mHiddenIndexAllKeys, &mFilterByHidden, &mHiddenIndexFilteredKeys, "hidden", false, "", "HIDDEN"}, {BROKEN_FILTER, &mBrokenIndexAllKeys, &mFilterByBroken, &mBrokenIndexFilteredKeys, "broken", false, "", "BROKEN"}, + {CONTROLLER_FILTER, &mControllerIndexAllKeys, &mFilterByController, &mControllerIndexFilteredKeys, "controller", false, "", "CONTROLLER BADGE"}, {ALTEMULATOR_FILTER, &mAltemulatorIndexAllKeys, &mFilterByAltemulator, &mAltemulatorIndexFilteredKeys, "altemulator", false, "", "ALTERNATIVE EMULATOR"} }; // clang-format on @@ -82,6 +84,7 @@ void FileFilterIndex::importIndex(FileFilterIndex* indexToImport) {&mKidGameIndexAllKeys, &(indexToImport->mKidGameIndexAllKeys)}, {&mHiddenIndexAllKeys, &(indexToImport->mHiddenIndexAllKeys)}, {&mBrokenIndexAllKeys, &(indexToImport->mBrokenIndexAllKeys)}, + {&mControllerIndexAllKeys, &(indexToImport->mControllerIndexAllKeys)}, {&mAltemulatorIndexAllKeys, &(indexToImport->mAltemulatorIndexAllKeys)}, }; @@ -119,6 +122,7 @@ void FileFilterIndex::resetIndex() clearIndex(mKidGameIndexAllKeys); clearIndex(mHiddenIndexAllKeys); clearIndex(mBrokenIndexAllKeys); + clearIndex(mControllerIndexAllKeys); clearIndex(mAltemulatorIndexAllKeys); } @@ -215,6 +219,12 @@ std::string FileFilterIndex::getIndexableKey(FileData* game, key = Utils::String::toUpper(game->metadata.get("broken")); break; } + case CONTROLLER_FILTER: { + if (getSecondary) + break; + key = Utils::String::toUpper(game->metadata.get("controller")); + break; + } case ALTEMULATOR_FILTER: { if (getSecondary) break; @@ -231,8 +241,8 @@ std::string FileFilterIndex::getIndexableKey(FileData* game, type == PUBLISHER_FILTER) && Utils::String::toUpper(key) == UNKNOWN_LABEL) key = ViewController::CROSSEDCIRCLE_CHAR + " UNKNOWN"; - else if (type == ALTEMULATOR_FILTER && key.empty()) - key = ViewController::CROSSEDCIRCLE_CHAR + " NONE DEFINED"; + else if ((type == CONTROLLER_FILTER || type == ALTEMULATOR_FILTER) && key.empty()) + key = ViewController::CROSSEDCIRCLE_CHAR + " NONE SELECTED"; else if (key.empty() || (type == RATINGS_FILTER && key == "0 STARS")) key = UNKNOWN_LABEL; @@ -251,6 +261,7 @@ void FileFilterIndex::addToIndex(FileData* game) manageKidGameEntryInIndex(game); manageHiddenEntryInIndex(game); manageBrokenEntryInIndex(game); + manageControllerEntryInIndex(game); manageAltemulatorEntryInIndex(game); } @@ -266,6 +277,7 @@ void FileFilterIndex::removeFromIndex(FileData* game) manageKidGameEntryInIndex(game, true); manageHiddenEntryInIndex(game, true); manageBrokenEntryInIndex(game, true); + manageControllerEntryInIndex(game, true); manageAltemulatorEntryInIndex(game, true); } @@ -365,6 +377,9 @@ void FileFilterIndex::debugPrintIndexes() for (auto x : mBrokenIndexAllKeys) { LOG(LogInfo) << "Broken Index: " << x.first << ": " << x.second; } + for (auto x : mControllerIndexAllKeys) { + LOG(LogInfo) << "Controller Index: " << x.first << ": " << x.second; + } for (auto x : mAltemulatorIndexAllKeys) { LOG(LogInfo) << "Altemulator Index: " << x.first << ": " << x.second; } @@ -444,28 +459,29 @@ bool FileFilterIndex::isFiltered() if (UIModeController::getInstance()->isUIModeKid()) { return (mFilterByText || mFilterByRatings || mFilterByDeveloper || mFilterByPublisher || mFilterByGenre || mFilterByPlayers || mFilterByFavorites || mFilterByCompleted || - mFilterByHidden || mFilterByBroken || mFilterByAltemulator); + mFilterByHidden || mFilterByBroken || mFilterByController || mFilterByAltemulator); } else { return (mFilterByText || mFilterByRatings || mFilterByDeveloper || mFilterByPublisher || mFilterByGenre || mFilterByPlayers || mFilterByFavorites || mFilterByCompleted || - mFilterByKidGame || mFilterByHidden || mFilterByBroken || mFilterByAltemulator); + mFilterByKidGame || mFilterByHidden || mFilterByBroken || mFilterByController || + mFilterByAltemulator); } } bool FileFilterIndex::isKeyBeingFilteredBy(std::string key, FilterIndexType type) { - const FilterIndexType filterTypes[11] = {RATINGS_FILTER, DEVELOPER_FILTER, PUBLISHER_FILTER, - GENRE_FILTER, PLAYER_FILTER, FAVORITES_FILTER, - COMPLETED_FILTER, KIDGAME_FILTER, HIDDEN_FILTER, - BROKEN_FILTER, ALTEMULATOR_FILTER}; - std::vector filterKeysList[11] = { + const FilterIndexType filterTypes[12] = { + RATINGS_FILTER, DEVELOPER_FILTER, PUBLISHER_FILTER, GENRE_FILTER, + PLAYER_FILTER, FAVORITES_FILTER, COMPLETED_FILTER, KIDGAME_FILTER, + HIDDEN_FILTER, BROKEN_FILTER, CONTROLLER_FILTER, ALTEMULATOR_FILTER}; + std::vector filterKeysList[12] = { mRatingsIndexFilteredKeys, mDeveloperIndexFilteredKeys, mPublisherIndexFilteredKeys, mGenreIndexFilteredKeys, mPlayersIndexFilteredKeys, mFavoritesIndexFilteredKeys, mCompletedIndexFilteredKeys, mKidGameIndexFilteredKeys, mHiddenIndexFilteredKeys, - mBrokenIndexFilteredKeys, mAltemulatorIndexFilteredKeys}; + mBrokenIndexFilteredKeys, mControllerIndexFilteredKeys, mAltemulatorIndexFilteredKeys}; - for (int i = 0; i < 11; i++) { + for (int i = 0; i < 12; i++) { if (filterTypes[i] == type) { for (std::vector::const_iterator it = filterKeysList[i].cbegin(); it != filterKeysList[i].cend(); it++) { @@ -611,6 +627,12 @@ void FileFilterIndex::manageBrokenEntryInIndex(FileData* game, bool remove) manageIndexEntry(&mBrokenIndexAllKeys, key, remove); } +void FileFilterIndex::manageControllerEntryInIndex(FileData* game, bool remove) +{ + std::string key = getIndexableKey(game, CONTROLLER_FILTER, false); + manageIndexEntry(&mControllerIndexAllKeys, key, remove); +} + void FileFilterIndex::manageAltemulatorEntryInIndex(FileData* game, bool remove) { std::string key = getIndexableKey(game, ALTEMULATOR_FILTER, false); diff --git a/es-app/src/FileFilterIndex.h b/es-app/src/FileFilterIndex.h index d4d744a7f..975c58432 100644 --- a/es-app/src/FileFilterIndex.h +++ b/es-app/src/FileFilterIndex.h @@ -31,6 +31,7 @@ enum FilterIndexType { KIDGAME_FILTER, HIDDEN_FILTER, BROKEN_FILTER, + CONTROLLER_FILTER, ALTEMULATOR_FILTER }; @@ -82,6 +83,7 @@ private: void manageKidGameEntryInIndex(FileData* game, bool remove = false); void manageHiddenEntryInIndex(FileData* game, bool remove = false); void manageBrokenEntryInIndex(FileData* game, bool remove = false); + void manageControllerEntryInIndex(FileData* game, bool remove = false); void manageAltemulatorEntryInIndex(FileData* game, bool remove = false); void manageIndexEntry(std::map* index, std::string key, bool remove); @@ -102,6 +104,7 @@ private: bool mFilterByKidGame; bool mFilterByHidden; bool mFilterByBroken; + bool mFilterByController; bool mFilterByAltemulator; std::map mRatingsIndexAllKeys; @@ -114,6 +117,7 @@ private: std::map mKidGameIndexAllKeys; std::map mHiddenIndexAllKeys; std::map mBrokenIndexAllKeys; + std::map mControllerIndexAllKeys; std::map mAltemulatorIndexAllKeys; std::vector mRatingsIndexFilteredKeys; @@ -126,6 +130,7 @@ private: std::vector mKidGameIndexFilteredKeys; std::vector mHiddenIndexFilteredKeys; std::vector mBrokenIndexFilteredKeys; + std::vector mControllerIndexFilteredKeys; std::vector mAltemulatorIndexFilteredKeys; }; diff --git a/es-app/src/guis/GuiGamelistFilter.cpp b/es-app/src/guis/GuiGamelistFilter.cpp index 6feda35e7..7721e38bf 100644 --- a/es-app/src/guis/GuiGamelistFilter.cpp +++ b/es-app/src/guis/GuiGamelistFilter.cpp @@ -11,9 +11,11 @@ #include "guis/GuiGamelistFilter.h" #include "SystemData.h" +#include "components/BadgeComponent.h" #include "components/OptionListComponent.h" #include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditPopup.h" +#include "utils/StringUtil.h" #include "views/UIModeController.h" #include "views/ViewController.h" @@ -185,8 +187,21 @@ void GuiGamelistFilter::addFiltersToMenu() optionList->setOverrideMultiText("NOTHING TO FILTER"); } - for (auto it : *allKeys) - optionList->add(it.first, it.first, mFilterIndex->isKeyBeingFilteredBy(it.first, type)); + if (type == CONTROLLER_FILTER) { + for (auto it : *allKeys) { + std::string displayName = + BadgeComponent::getDisplayName(Utils::String::toLower(it.first)); + if (displayName == "unknown") + displayName = it.first; + optionList->add(displayName, it.first, + mFilterIndex->isKeyBeingFilteredBy(it.first, type)); + } + } + else { + for (auto it : *allKeys) + optionList->add(it.first, it.first, + mFilterIndex->isKeyBeingFilteredBy(it.first, type)); + } if (allKeys->size() == 0) optionList->add("", "", false); From 593cfdbdd72ffae58073d67fec50dfda12792e19 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 14:11:19 +0200 Subject: [PATCH 104/128] Fixed multiple issues where menu sizes were not properly initialized. --- es-app/src/guis/GuiMenu.cpp | 6 ++++++ es-app/src/guis/GuiScreensaverOptions.cpp | 5 +++++ es-app/src/guis/GuiScreensaverOptions.h | 2 ++ 3 files changed, 13 insertions(+) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index ffa9cd0f2..58bf31976 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -593,6 +593,7 @@ void GuiMenu::openUIOptions() } }); + s->setSize(mSize); mWindow->pushGui(s); } @@ -700,6 +701,7 @@ void GuiMenu::openSoundOptions() }); } + s->setSize(mSize); mWindow->pushGui(s); } @@ -756,6 +758,7 @@ void GuiMenu::openInputDeviceOptions() configure_input_row.makeAcceptInputHandler(std::bind(&GuiMenu::openConfigInput, this, s)); s->addRow(configure_input_row); + s->setSize(mSize); mWindow->pushGui(s); } @@ -1180,12 +1183,14 @@ void GuiMenu::openOtherOptions() run_in_background->setCallback(launchWorkaroundToggleFunc); #endif + s->setSize(mSize); mWindow->pushGui(s); } void GuiMenu::openUtilitiesMenu() { auto s = new GuiSettings(mWindow, "UTILITIES"); + s->setSize(mSize); mWindow->pushGui(s); } @@ -1263,6 +1268,7 @@ void GuiMenu::openQuitMenu() row.addElement(powerOffText, true); s->addRow(row); + s->setSize(mSize); mWindow->pushGui(s); } } diff --git a/es-app/src/guis/GuiScreensaverOptions.cpp b/es-app/src/guis/GuiScreensaverOptions.cpp index ea6eaf037..cb5bffae2 100644 --- a/es-app/src/guis/GuiScreensaverOptions.cpp +++ b/es-app/src/guis/GuiScreensaverOptions.cpp @@ -17,6 +17,7 @@ GuiScreensaverOptions::GuiScreensaverOptions(Window* window, const std::string& title) : GuiSettings(window, title) + , mWindow(window) { // Screensaver timer. auto screensaver_timer = std::make_shared(mWindow, 0.0f, 30.0f, 1.0f, "m"); @@ -95,6 +96,8 @@ GuiScreensaverOptions::GuiScreensaverOptions(Window* window, const std::string& row.makeAcceptInputHandler( std::bind(&GuiScreensaverOptions::openVideoScreensaverOptions, this)); addRow(row); + + setSize(getMenuSize()); } void GuiScreensaverOptions::openSlideshowScreensaverOptions() @@ -206,6 +209,7 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions() } }); + s->setSize(mSize); mWindow->pushGui(s); } @@ -287,5 +291,6 @@ void GuiScreensaverOptions::openVideoScreensaverOptions() }); #endif + s->setSize(mSize); mWindow->pushGui(s); } diff --git a/es-app/src/guis/GuiScreensaverOptions.h b/es-app/src/guis/GuiScreensaverOptions.h index 9423ab6fe..1fe1993e3 100644 --- a/es-app/src/guis/GuiScreensaverOptions.h +++ b/es-app/src/guis/GuiScreensaverOptions.h @@ -18,6 +18,8 @@ public: GuiScreensaverOptions(Window* window, const std::string& title); private: + Window* mWindow; + void openSlideshowScreensaverOptions(); void openVideoScreensaverOptions(); }; From 71b5c50b8594814f2e34e485aa15639390ac9844 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 14:17:43 +0200 Subject: [PATCH 105/128] Made the SliderComponent width consistent across different screen aspect ratios. --- es-core/src/components/SliderComponent.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/es-core/src/components/SliderComponent.cpp b/es-core/src/components/SliderComponent.cpp index 07eb7d36c..77f837529 100644 --- a/es-core/src/components/SliderComponent.cpp +++ b/es-core/src/components/SliderComponent.cpp @@ -8,6 +8,7 @@ #include "components/SliderComponent.h" +#include "Window.h" #include "resources/Font.h" #define MOVE_REPEAT_DELAY 500 @@ -32,7 +33,7 @@ SliderComponent::SliderComponent( mKnob.setOrigin(0.5f, 0.5f); mKnob.setImage(":/graphics/slider_knob.svg"); - setSize(Renderer::getScreenWidth() * 0.15f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()); + setSize(window->peekGui()->getSize().x * 0.26f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()); } bool SliderComponent::input(InputConfig* config, Input input) From b83db7d33e0c06259d2bd19f75908612a64d6bb0 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 14:48:42 +0200 Subject: [PATCH 106/128] Added text debug overlay to SliderComponent. Also changed a variable name to align with the standard naming conventions. --- es-core/src/components/SliderComponent.cpp | 26 ++++++++++++++-------- es-core/src/components/SliderComponent.h | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/es-core/src/components/SliderComponent.cpp b/es-core/src/components/SliderComponent.cpp index 77f837529..ddcb981e1 100644 --- a/es-core/src/components/SliderComponent.cpp +++ b/es-core/src/components/SliderComponent.cpp @@ -81,15 +81,23 @@ void SliderComponent::render(const glm::mat4& parentTrans) glm::mat4 trans{parentTrans * getTransform()}; Renderer::setMatrix(trans); - // Render suffix. - if (mValueCache) - mFont->renderTextCache(mValueCache.get()); + if (Settings::getInstance()->getBool("DebugText")) { + Renderer::drawRect(mSize.x - mTextCache->metrics.size.x, 0.0f, mTextCache->metrics.size.x, + mSize.y, 0x0000FF33, 0x0000FF33); + Renderer::drawRect( + mSize.x - mTextCache->metrics.size.x, (mSize.y - mTextCache->metrics.size.y) / 2.0f, + mTextCache->metrics.size.x, mTextCache->metrics.size.y, 0x0000FF33, 0x0000FF33); + } float width{mSize.x - mKnob.getSize().x - - (mValueCache ? - mValueCache->metrics.size.x + (4.0f * Renderer::getScreenWidthModifier()) : + (mTextCache ? + mTextCache->metrics.size.x + (4.0f * Renderer::getScreenWidthModifier()) : 0.0f)}; + // Render suffix. + if (mTextCache) + mFont->renderTextCache(mTextCache.get()); + // Render bar. Renderer::drawRect(mKnob.getSize().x / 2.0f, mSize.y / 2.0f - mBarHeight / 2.0f, width, mBarHeight, 0x777777FF, 0x777777FF); @@ -139,17 +147,17 @@ void SliderComponent::onValueChanged() const std::string max = ss.str(); glm::vec2 textSize = mFont->sizeText(max); - mValueCache = std::shared_ptr(mFont->buildTextCache( + mTextCache = std::shared_ptr(mFont->buildTextCache( val, mSize.x - textSize.x, (mSize.y - textSize.y) / 2.0f, 0x777777FF)); - mValueCache->metrics.size.x = textSize.x; // Fudge the width. + mTextCache->metrics.size.x = textSize.x; // Fudge the width. } mKnob.setResize(0.0f, std::round(mSize.y * 0.7f)); float barLength = mSize.x - mKnob.getSize().x - - (mValueCache ? mValueCache->metrics.size.x + (4.0f * Renderer::getScreenWidthModifier()) : - 0.0f); + (mTextCache ? mTextCache->metrics.size.x + (4.0f * Renderer::getScreenWidthModifier()) : + 0.0f); int barHeight = static_cast(std::round(2.0f * Renderer::getScreenHeightModifier())); diff --git a/es-core/src/components/SliderComponent.h b/es-core/src/components/SliderComponent.h index 9392364d9..2ca75cb9a 100644 --- a/es-core/src/components/SliderComponent.h +++ b/es-core/src/components/SliderComponent.h @@ -52,7 +52,7 @@ private: std::string mSuffix; std::shared_ptr mFont; - std::shared_ptr mValueCache; + std::shared_ptr mTextCache; }; #endif // ES_CORE_COMPONENTS_SLIDER_COMPONENT_H From 960f6e56be5b02409bd3055726005239f6328545 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 14:53:53 +0200 Subject: [PATCH 107/128] Fixed an incorrect text debug overlay color in SliderComponent. --- es-core/src/components/SliderComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/SliderComponent.cpp b/es-core/src/components/SliderComponent.cpp index ddcb981e1..bcf0ca9d9 100644 --- a/es-core/src/components/SliderComponent.cpp +++ b/es-core/src/components/SliderComponent.cpp @@ -82,11 +82,11 @@ void SliderComponent::render(const glm::mat4& parentTrans) Renderer::setMatrix(trans); if (Settings::getInstance()->getBool("DebugText")) { - Renderer::drawRect(mSize.x - mTextCache->metrics.size.x, 0.0f, mTextCache->metrics.size.x, - mSize.y, 0x0000FF33, 0x0000FF33); Renderer::drawRect( mSize.x - mTextCache->metrics.size.x, (mSize.y - mTextCache->metrics.size.y) / 2.0f, mTextCache->metrics.size.x, mTextCache->metrics.size.y, 0x0000FF33, 0x0000FF33); + Renderer::drawRect(mSize.x - mTextCache->metrics.size.x, 0.0f, mTextCache->metrics.size.x, + mSize.y, 0x00000033, 0x00000033); } float width{mSize.x - mKnob.getSize().x - From 0e959c3c613231616d78f24f16b2810ef0c27da4 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 18:20:56 +0200 Subject: [PATCH 108/128] Made the 'switch' system logo smaller. --- themes/rbsimple-DE/switch/images/logo.svg | 32 +++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/themes/rbsimple-DE/switch/images/logo.svg b/themes/rbsimple-DE/switch/images/logo.svg index 04414e24f..5ca4ddafb 100644 --- a/themes/rbsimple-DE/switch/images/logo.svg +++ b/themes/rbsimple-DE/switch/images/logo.svg @@ -7,10 +7,10 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="2922" - height="644" + width="800" + height="176.36418" version="1.1" - viewBox="0 0 2922 644" + viewBox="0 0 800 176.36418" id="svg2" sodipodi:docname="logo.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> @@ -41,31 +41,35 @@ inkscape:window-height="2065" id="namedview4" showgrid="false" - inkscape:zoom="0.30969238" - inkscape:cx="338.77838" - inkscape:cy="458.73741" + inkscape:zoom="2.184783" + inkscape:cx="366.47128" + inkscape:cy="193.80534" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="svg2" /> + inkscape:current-layer="svg2" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> + d="M 37.76031,0.58425297 C 20.19122,3.7246229 6.0555905,16.724283 1.5993975,33.813713 c -1.60715171,6.17119 -1.71673023,9.78626 -1.53409936,57.6586 0.10957852,43.965127 0.14610469,44.987577 0.87662817,48.383547 4.05440539,18.29447 17.05772269,31.33064 35.53996669,35.63952 2.410727,0.54774 5.478927,0.65728 25.239586,0.76683 20.454656,0.14606 22.573174,0.10955 23.121068,-0.43819 0.547893,-0.54774 0.584418,-7.5953 0.584418,-87.492077 0,-59.22878 -0.109579,-87.1269201 -0.365262,-87.63814003 -0.365262,-0.65729 -0.986206,-0.6938000088983 -22.390543,-0.65729 -17.386458,0.0365 -22.609702,0.14607 -24.91085,0.54774 z M 70.816496,88.222393 v 73.981197 l -14.866152,-0.18258 c -13.697316,-0.14606 -15.158362,-0.21909 -17.897826,-0.91289 -11.761427,-3.03082 -20.491183,-12.08677 -22.938436,-23.8814 -0.803576,-3.6881 -0.803576,-94.539647 -0.03653,-98.154717 2.191571,-10.26096 9.314175,-18.76917 18.920557,-22.60334 4.821456,-1.93534 7.049552,-2.15443 22.682754,-2.19095 l 14.13563,-0.0365 z" /> + d="m 40.901559,36.552403 c -2.301147,0.4382 -5.807661,2.19096 -7.670494,3.83417 -3.83525,3.32295 -5.734609,8.0335 -5.442401,13.58392 0.146105,2.88475 0.328736,3.65158 1.497574,5.9886 1.716728,3.54204 4.310086,6.13467 7.853125,7.88744 2.447255,1.20502 3.0682,1.35108 6.245977,1.46063 2.885568,0.10955 3.908301,0 5.844189,-0.65729 7.926179,-2.66565 12.711107,-10.37051 11.359638,-18.25794 -1.570626,-9.38459 -10.483011,-15.66532 -19.687608,-13.83953 z" /> + d="m 103.21521,0.25561297 c -0.14611,0.10955 -0.25568,39.69277003 -0.25568,87.96678003 0,79.641157 0.0365,87.711177 0.58441,87.930267 0.98621,0.36516 29.33052,0.2191 32.83703,-0.14606 14.82963,-1.67973 27.906,-10.69916 35.0286,-24.10049 0.91316,-1.71625 2.11852,-4.601 2.73947,-6.39028 2.30115,-6.86499 2.22809,-5.03919 2.22809,-57.512527 0,-41.88373 -0.073,-48.12795 -0.58441,-50.79361 C 172.17662,18.184913 157.67573,3.8341629 138.60907,0.54773297 136.05224,0.10954297 132.10741,2.9611016e-6 119.3963,2.9611016e-6 110.63002,2.9611016e-6 103.32479,0.10954297 103.21521,0.25561297 Z M 142.22516,79.787223 c 5.69808,1.49715 10.37343,5.84254 12.2728,11.39296 1.20536,3.4325 1.16883,8.47169 -0.0365,11.612047 -2.2281,5.76952 -6.61124,9.82278 -12.23627,11.31993 -9.13154,2.37353 -18.84751,-3.14037 -21.51391,-12.19631 -0.80358,-2.775207 -0.76705,-7.449237 0.1461,-10.297477 2.73946,-8.94639 12.23627,-14.20468 21.36781,-11.83115 z" /> + transform="matrix(0.62745924,0,0,0.62745924,-274.21022,-113.86845)"> Date: Sun, 24 Oct 2021 18:21:36 +0200 Subject: [PATCH 109/128] (rbsimple-DE) Increased the size of the controller icons. --- themes/rbsimple-DE/theme.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 130467fbc..7d543fe59 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -244,7 +244,7 @@ based on: 'recalbox-multi' by the Recalbox community 3 2 0.5 0.572 - 0.67 + 0.81 -1.0 0.005 favorite, completed, kidgame, broken, controller, altemulator From 3ea255731495cee80338fbc0b637e67dc2909d92 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 18:23:13 +0200 Subject: [PATCH 110/128] Added some controller graphics. --- .../controllers/gamepad_nintendo_64.svg | 251 +++++++++++++- .../controllers/gamepad_nintendo_nes.svg | 289 +++++++++++++++- .../controllers/gamepad_nintendo_snes.svg | 213 +++++++++++- .../controllers/gamepad_playstation.svg | 118 ++++++- .../joycon_left_or_right_nintendo.svg | 269 ++++++++++++++- .../controllers/joycon_pair_nintendo.svg | 267 +++++++++++++- .../graphics/controllers/mouse_amiga.svg | 67 +++- .../controllers/wii_remote_nintendo.svg | 289 +++++++++++++++- .../wii_remote_nunchuck_nintendo.svg | 325 +++++++++++++++++- 9 files changed, 1976 insertions(+), 112 deletions(-) diff --git a/resources/graphics/controllers/gamepad_nintendo_64.svg b/resources/graphics/controllers/gamepad_nintendo_64.svg index b6b01c15a..48c9bf4e8 100644 --- a/resources/graphics/controllers/gamepad_nintendo_64.svg +++ b/resources/graphics/controllers/gamepad_nintendo_64.svg @@ -15,9 +15,34 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="gamepad_nintendo_64.svg"> + id="defs4919"> + + + + + + + + + + + inkscape:window-maximized="1" + inkscape:snap-bbox="false" /> @@ -55,14 +81,213 @@ id="layer1" transform="translate(0,-285.88748)"> + id="g6500"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_nintendo_nes.svg b/resources/graphics/controllers/gamepad_nintendo_nes.svg index b6b01c15a..e546a52eb 100644 --- a/resources/graphics/controllers/gamepad_nintendo_nes.svg +++ b/resources/graphics/controllers/gamepad_nintendo_nes.svg @@ -15,7 +15,7 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="gamepad_nintendo_nes.svg"> + inkscape:window-maximized="1" + inkscape:snap-global="false" /> @@ -55,14 +56,278 @@ id="layer1" transform="translate(0,-285.88748)"> + id="g5404" + transform="matrix(0.98413614,0,0,0.98413614,0.08814355,4.6234203)"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_nintendo_snes.svg b/resources/graphics/controllers/gamepad_nintendo_snes.svg index b6b01c15a..f4e823894 100644 --- a/resources/graphics/controllers/gamepad_nintendo_snes.svg +++ b/resources/graphics/controllers/gamepad_nintendo_snes.svg @@ -2,6 +2,7 @@ + sodipodi:docname="gamepad_nintendo_snes.svg"> + id="g5490" + transform="translate(-1e-7,3.169167e-6)"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/controllers/gamepad_playstation.svg b/resources/graphics/controllers/gamepad_playstation.svg index b6b01c15a..5b5c4016e 100644 --- a/resources/graphics/controllers/gamepad_playstation.svg +++ b/resources/graphics/controllers/gamepad_playstation.svg @@ -15,9 +15,18 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="gamepad_playstation.svg"> + id="defs4919"> + + + + + id="g14725" + transform="translate(4.3347377e-7,2.6668856e-5)"> + inkscape:connector-curvature="0" + id="path30-4" + d="m 2.918825,288.73845 c -0.3626261,0 -0.616968,0.0767 -0.6457872,0.22565 -0.01881,0.097 -0.050224,0.30821 -0.078304,0.49129 -0.41776,0.24637 -0.6988306,0.69994 -0.6988306,1.22 0,0.0214 0.00244,0.0421 0.00334,0.0631 -0.162574,0.97662 -0.2776564,1.9435 -0.3645706,2.39876 -0.15520473,0.81336 1.2039422,1.49728 1.7192908,0.55737 0.2440554,-0.44511 0.4645608,-0.87975 0.6276849,-1.20611 0.1572849,0.27068 0.4470001,0.45466 0.7826057,0.45466 0.3725061,0 0.6916645,-0.22473 0.8318607,-0.54559 h 0.9038486 c 0.1406855,0.31961 0.458946,0.5435 0.8305979,0.5435 0.3414904,0 0.6363897,-0.19031 0.7914465,-0.46899 0.164161,0.32858 0.3886006,0.7719 0.6356832,1.22253 0.5153483,0.93993 1.8802691,0.25485 1.7192919,-0.55737 -0.088057,-0.44424 -0.2047877,-1.44363 -0.3708866,-2.43665 9.23e-5,-0.006 8.063e-4,-0.0131 8.063e-4,-0.0198 0,-0.53576 -0.2975275,-1.00202 -0.7362977,-1.24274 -0.026471,-0.17238 -0.057632,-0.38042 -0.075777,-0.47403 -0.028798,-0.14887 -0.2831807,-0.22565 -0.6457867,-0.22565 -0.3626265,0 -0.6169468,0.0768 -0.6457865,0.22565 -0.020408,0.10559 -0.053047,0.32285 -0.085459,0.53464 -0.043019,0.0282 -0.087115,0.055 -0.1267154,0.0876 H 5.8530379 c -0.010012,0.0164 -0.028546,0.0278 -0.049255,0.0278 H 5.2775551 c -0.020687,0 -0.03798,-0.0113 -0.047992,-0.0278 h -1.411553 c -0.054123,-0.045 -0.1129686,-0.0841 -0.1734445,-0.12082 -0.029054,-0.1895 -0.060403,-0.40239 -0.079566,-0.5014 -0.028798,-0.14887 -0.2836438,-0.22565 -0.6462072,-0.22565 z" + style="fill:#c2c2c2;fill-opacity:1;fill-rule:nonzero;stroke:#010101;stroke-width:0.12790291;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/controllers/joycon_left_or_right_nintendo.svg b/resources/graphics/controllers/joycon_left_or_right_nintendo.svg index b6b01c15a..e4e991891 100644 --- a/resources/graphics/controllers/joycon_left_or_right_nintendo.svg +++ b/resources/graphics/controllers/joycon_left_or_right_nintendo.svg @@ -15,7 +15,7 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="joycon_left_or_right_nintendo.svg"> image/svg+xml - + @@ -55,15 +55,258 @@ id="layer1" transform="translate(0,-285.88748)"> + id="g5555-7" + transform="translate(-0.02566855,1.3229153)"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + inkscape:transform-center-y="-0.036071626" + d="m 2.9824786,291.04147 0.1249556,0.21643 -0.2499112,0 z" + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="true" + sodipodi:arg2="-0.52359878" + sodipodi:arg1="-1.5707963" + sodipodi:r2="0.072143152" + sodipodi:r1="0.1442863" + sodipodi:cy="291.18576" + sodipodi:cx="2.9824786" + sodipodi:sides="3" + id="path5401-0" + style="opacity:1;vector-effect:none;fill:#282828;fill-opacity:1;stroke:none;stroke-width:0.08117354;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" + sodipodi:type="star" /> + + + diff --git a/resources/graphics/controllers/joycon_pair_nintendo.svg b/resources/graphics/controllers/joycon_pair_nintendo.svg index b6b01c15a..37aec1f15 100644 --- a/resources/graphics/controllers/joycon_pair_nintendo.svg +++ b/resources/graphics/controllers/joycon_pair_nintendo.svg @@ -15,7 +15,7 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="joycon_pair_nintendo.svg"> + id="g5555" + transform="translate(-0.02566855,-1.3888992e-6)"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + inkscape:transform-center-y="-0.036071626" + d="m 2.9824786,291.04147 0.1249556,0.21643 -0.2499112,0 z" + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="true" + sodipodi:arg2="-0.52359878" + sodipodi:arg1="-1.5707963" + sodipodi:r2="0.072143152" + sodipodi:r1="0.1442863" + sodipodi:cy="291.18576" + sodipodi:cx="2.9824786" + sodipodi:sides="3" + id="path5401" + style="opacity:1;vector-effect:none;fill:#282828;fill-opacity:1;stroke:none;stroke-width:0.08117354;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" + sodipodi:type="star" /> + + + diff --git a/resources/graphics/controllers/mouse_amiga.svg b/resources/graphics/controllers/mouse_amiga.svg index b6b01c15a..696cbd88d 100644 --- a/resources/graphics/controllers/mouse_amiga.svg +++ b/resources/graphics/controllers/mouse_amiga.svg @@ -15,7 +15,7 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="mouse_amiga.svg"> + inkscape:window-maximized="1" + inkscape:snap-bbox="false" + inkscape:snap-global="false" /> @@ -55,15 +57,54 @@ id="layer1" transform="translate(0,-285.88748)"> + id="g5117"> + inkscape:connector-curvature="0" + id="rect4891" + d="m 5.4289836,285.88748 h 0.2545328 v 2.06353 H 5.4289836 Z" + style="opacity:1;vector-effect:none;fill:#60605d;fill-opacity:1;stroke:none;stroke-width:0.04231874;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + + + + + + + + + + + diff --git a/resources/graphics/controllers/wii_remote_nintendo.svg b/resources/graphics/controllers/wii_remote_nintendo.svg index b6b01c15a..c8a5055c7 100644 --- a/resources/graphics/controllers/wii_remote_nintendo.svg +++ b/resources/graphics/controllers/wii_remote_nintendo.svg @@ -15,7 +15,7 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="wii_remote_nintendo.svg"> + id="g5266"> + inkscape:connector-curvature="0" + id="path6" + d="m 4.492749,286.76443 c -0.150954,0 -0.274402,0.12397 -0.274402,0.27492 v 9.05681 c 0,0.15095 0.123448,0.27492 0.274402,0.27492 h 2.1270018 c 0.1509541,0 0.2744021,-0.12397 0.2744021,-0.27492 v -9.05681 c 0,-0.15095 -0.123448,-0.27492 -0.2744021,-0.27492 z" + style="fill:#f8f8f8;stroke:#000000;stroke-width:0.079375;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/controllers/wii_remote_nunchuck_nintendo.svg b/resources/graphics/controllers/wii_remote_nunchuck_nintendo.svg index b6b01c15a..d9699c383 100644 --- a/resources/graphics/controllers/wii_remote_nunchuck_nintendo.svg +++ b/resources/graphics/controllers/wii_remote_nunchuck_nintendo.svg @@ -15,7 +15,7 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="wii_remote_nunchuck_nintendo.svg"> + inkscape:window-maximized="1" + inkscape:snap-bbox="true" /> @@ -55,15 +56,313 @@ id="layer1" transform="translate(0,-285.88748)"> + id="g5629" + transform="matrix(0.97763205,0,0,0.97763205,0.12423766,6.4933408)"> + id="path188" + d="m 3.330473,296.25878 c -0.6814899,0 -0.8835396,-1.82872 -1.0821648,-3.5958 -0.034246,-0.29793 -0.068491,-0.60615 -0.1027371,-0.89723 -0.1678041,-1.35271 0.1335583,-2.01708 0.4177977,-2.33556 0.2260218,-0.25684 0.5239596,-0.3904 0.8629922,-0.3904 0.3390326,0 0.6369704,0.13356 0.8629921,0.3904 0.2808149,0.31848 0.5821773,0.98285 0.4177978,2.33556 -0.034246,0.29108 -0.068491,0.5993 -0.1027372,0.89723 -0.1952006,1.76708 -0.4006749,3.5958 -1.0821647,3.5958 z" + inkscape:connector-curvature="0" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.10556875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5b7483c4d73dedeec94900fe3b2cccf312b6965f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 18:30:23 +0200 Subject: [PATCH 111/128] Adjusted two controller graphics files. --- .../joycon_left_or_right_nintendo.svg | 136 +++++++++--------- .../controllers/joycon_pair_nintendo.svg | 78 +++++----- 2 files changed, 113 insertions(+), 101 deletions(-) diff --git a/resources/graphics/controllers/joycon_left_or_right_nintendo.svg b/resources/graphics/controllers/joycon_left_or_right_nintendo.svg index e4e991891..3a8554f7d 100644 --- a/resources/graphics/controllers/joycon_left_or_right_nintendo.svg +++ b/resources/graphics/controllers/joycon_left_or_right_nintendo.svg @@ -25,9 +25,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="24.884308" - inkscape:cx="13.368346" - inkscape:cy="25.181666" + inkscape:zoom="17.595863" + inkscape:cx="-0.77044279" + inkscape:cy="30.632636" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" @@ -55,83 +55,84 @@ id="layer1" transform="translate(0,-285.88748)"> + id="g4910" + transform="matrix(1.0578414,0,0,1.0578414,-0.32138125,-15.666888)"> + transform="translate(-3.8154512,-1.3888992e-6)" + id="g5517"> + transform="translate(0.07832299,-1.3888992e-6)" + id="g5361"> + transform="translate(-0.58238277,-1.3888992e-6)" + id="g5361-2"> + id="g4874" + transform="matrix(1.0578414,0,0,1.0578414,-0.32138125,-18.048138)"> + transform="translate(-0.02566855,-1.3888992e-6)" + id="g5474"> diff --git a/resources/graphics/controllers/joycon_pair_nintendo.svg b/resources/graphics/controllers/joycon_pair_nintendo.svg index 37aec1f15..649377f0a 100644 --- a/resources/graphics/controllers/joycon_pair_nintendo.svg +++ b/resources/graphics/controllers/joycon_pair_nintendo.svg @@ -17,7 +17,15 @@ inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" sodipodi:docname="joycon_pair_nintendo.svg"> + id="defs4919"> + + + id="g4910" + transform="matrix(1.0578414,0,0,1.0578414,-0.32138125,-16.857513)"> + id="g4874" + transform="matrix(1.0578414,0,0,1.0578414,-0.32138125,-16.857513)"> Date: Sun, 24 Oct 2021 18:38:14 +0200 Subject: [PATCH 112/128] Documentation update. --- CHANGELOG.md | 3 +++ CONTRIBUTING.md | 2 +- INSTALL-DEV.md | 2 ++ USERGUIDE-DEV.md | 10 ++++++---- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3069bc8f5..1322caa21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ * Added support for a new type of "flat style" button to ButtonComponent * Added support for correctly navigating arbitrarily sized ComponentGrid entries, i.e. those spanning multiple cells * Bundled the bold font version of Fontfabric Akrobat +* Moved the resources/help directory to resources/graphics/help +* Removed the unused graphics files resources/graphics/fav_add.svg and resources/graphics/fav_remove.svg * Added RapidJSON as a Git subtree * Added the GLM (OpenGL Mathematics) library as a Git subtree * Replaced all built-in matrix and vector data types and functions with GLM library equivalents @@ -100,6 +102,7 @@ * Really long theme set names would not get abbreviated in the UI settings menu, leading to a garbled "Theme set" setting row * Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a gamelist on startup had been set * When marking a game to not be counted in the metadata editor and the game was part of a custom collection, no collection disabling notification was displayed +* SliderComponent had very inconsistent widths at different screen aspect ratios * SliderComponent did not properly align the knob and bar vertically * Resizing in SwitchComponent did not reposition the image properly leading to a non-centered image * Horizontal sizing of the TextComponent input field was not consistent across different screen resolutions diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b37752bfe..e35e752ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,10 +50,10 @@ This plan is under constant review so expect it to change from time to time. Sti * Localization/multi-language support * Reorganize the menus, possibly adding basic/advanced modes * Authoring tools to clean up orphaned gamelist entries, media files etc. +* Add scraping of game manuals and maps and create a viewer for these (with PDF, GIF, JPG and PNG support) * Scrollbar component for the gamelist view which can be used by the themes * Web proxy support for the scraper * Add "time played" counter per game, similar to how it works in Steam -* Preload all built-in resources and never clear them from the cache * Improve multi-threading #### v1.5 diff --git a/INSTALL-DEV.md b/INSTALL-DEV.md index cd33d350f..cead7d5fd 100644 --- a/INSTALL-DEV.md +++ b/INSTALL-DEV.md @@ -1870,6 +1870,7 @@ There are two basic categories of metadata, `game` and `folders` and the metdata * `nomultiscrape` - bool, indicates whether the game should be excluded from the multi-scraper * `hidemetadata` - bool, indicates whether to hide most of the metadata fields when displaying the game in the gamelist view * `playcount` - integer, the number of times this game has been played +* `controller` - string, used to display controller badges * `altemulator` - string, overrides the emulator/launch command on a per game basis * `lastplayed` - statistic, datetime, the last date and time this game was played @@ -1891,6 +1892,7 @@ For folders, most of the fields are identical although some are removed. In the * `broken` * `nomultiscrape` * `hidemetadata` +* `controller` * `lastplayed` - statistic, for folders this is inherited by the latest game file launched inside the folder. **Additional gamelist.xml information:** diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 483fb7a3b..8669a463e 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -215,7 +215,7 @@ In addition to the styles just described, there is a **Grid** view style as well If the theme supports it, there's a gamelist information field displayed in the gamelist view, showing the number of games for the system (total and favorites) as well as a folder icon if a folder has been entered. When applying any filters to the gamelist, the game counter is replaced with the amount of games filtered, as in 'filtered / total games', e.g. '19 / 77'. If there are game entries in the filter result that are marked not to be counted as games, the number of such files will be indicated as 'filtered + filtered non-games / total games', for example '23 + 4 / 77' indicating 23 normal games, 4 non-games out of a total of 77. Due to this approach it's theoretically possible that the combined filtered game amount exceeds the number of counted games in the collection, for instance '69 + 11 / 77'. This is not considered a bug and is so by design. This gamelist information field functionality is specific to EmulationStation Desktop Edition so older themes will not support this. -Another feature which requires theme support is **Badges**, which is a set of icons displaying the status for various metadata fields. The currently supported badge types are _favorite, completed, kidgame, broken, controller_ and _alternative emulator_. If any of the first four metadata fields have been set for a game, their corresponding badges will be displayed. If a game-specific controller has been selected, the corresponding controller icon will be shown on the controller badge, and if an alternative emulator has been selected for the specific game, that badge will be shown. Setting an alternative emulator system-wide will not display this badge as it's only intended to indicate game-specific overrides. +Another feature which requires theme support is **Badges**, which is a set of icons displaying the status for various metadata fields. The currently supported badge types are _favorite, completed, kidgame, broken, controller_ and _alternative emulator_. If any of the first four metadata fields have been set for a game, their corresponding badges will be displayed. If a game-specific controller has been selected via the metadata editor, the corresponding controller badge will be shown. And if an alternative emulator has been selected for the specific game, that badge will be displayed. Setting an alternative emulator system-wide will not display this badge as it's only intended to indicate game-specific overrides. ![alt text](images/es-de_gamelist_view.png "ES-DE Gamelist View") _The **Gamelist view** is where you browse the games for a specific system._ @@ -1372,9 +1372,11 @@ The following filters can be applied: **Broken** +**Controller badge** + **Alternative emulator** -With the exception of the game name text filter, all available filter values are assembled from metadata from the actual gamelist, so if there is no data to filter for the specific field, the text _Nothing to filter_ will be displayed. This for example happens for the _Completed_ filter if there are no games marked as having been completed in the current gamelist. +With the exception of the game name text filter, all available filter values are assembled from metadata from the actual gamelist, so if there is no data to filter for the specific field, the text _Nothing to filter_ will be displayed. This for example happens for the _Completed_ filter if there are no games marked as having been completed in the current gamelist. The same happens if a metadata setting is identical for all games, such as all games being flagged as favorites. Be aware that although folders can have most of the metadata values set, the filters are only applied to files (this is also true for the game name text filter). So if you for example set a filter to only display your favorite games, any folder that contains a favorite game will be displayed, and other folders which are themselves marked as favorites but that do not contain any favorite games will be hidden. @@ -1476,9 +1478,9 @@ This option will hide most metadata fields as well as any badges. The intention A statistics counter that tracks how many times you have played the game. You normally don't need to touch this, but if you want to, the possibility is there. -**Controller** +**Controller badge** -Contains a list of controller images that are built into ES-DE. The selected controller will be displayed as a badge if the current theme set support badges. This functionality is only cosmetic and will not affect the actual emulators, and it will not affect the controller input for ES-DE itself. +This entry provides a selection of controller icons that are built into ES-DE (although the theme set can override the actual graphics files). The selected icon will be displayed as a badge if the current theme set support badges. This functionality is only cosmetic and will not affect the actual emulators. **Alternative emulator** _(files only)_ From b3022438ec02eb885806c3530c6be9314ef938f1 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 24 Oct 2021 21:25:06 +0200 Subject: [PATCH 113/128] Added a controller graphics file and updated two other. --- .../controllers/gamepad_playstation.svg | 186 +++++++++++-- .../graphics/controllers/joystick_generic.svg | 263 +++++++++++++++++- resources/graphics/controllers/unknown.svg | 8 +- 3 files changed, 415 insertions(+), 42 deletions(-) diff --git a/resources/graphics/controllers/gamepad_playstation.svg b/resources/graphics/controllers/gamepad_playstation.svg index 5b5c4016e..9ae7276e1 100644 --- a/resources/graphics/controllers/gamepad_playstation.svg +++ b/resources/graphics/controllers/gamepad_playstation.svg @@ -26,6 +26,22 @@ id="path18" d="M 0,428.623 H 265.712 V 0 H 0 Z" /> + + + + + + + id="g6486"> + + + d="m 2.9188254,288.73848 c -0.3626261,0 -0.616968,0.0767 -0.6457872,0.22565 -0.01881,0.097 -0.050224,0.30821 -0.078304,0.49129 -0.41776,0.24637 -0.6988306,0.69994 -0.6988306,1.22 0,0.0214 0.00244,0.0421 0.00334,0.0631 -0.162574,0.97662 -0.2776564,1.9435 -0.3645706,2.39876 -0.1552047,0.81336 1.2039422,1.49728 1.7192908,0.55737 0.2440554,-0.44511 0.4645608,-0.87975 0.6276849,-1.20611 0.1572849,0.27068 0.4470001,0.45466 0.7826057,0.45466 0.3725061,0 0.6916645,-0.22473 0.8318607,-0.54559 h 0.9038486 c 0.1406855,0.31961 0.458946,0.5435 0.8305979,0.5435 0.3414904,0 0.6363897,-0.19031 0.7914465,-0.46899 0.164161,0.32858 0.3886006,0.7719 0.6356832,1.22253 0.5153483,0.93993 1.8802687,0.25485 1.7192919,-0.55737 -0.088057,-0.44424 -0.2047877,-1.44363 -0.3708866,-2.43665 9.23e-5,-0.006 8.063e-4,-0.0131 8.063e-4,-0.0198 0,-0.53576 -0.2975275,-1.00202 -0.7362977,-1.24274 -0.026471,-0.17238 -0.057632,-0.38042 -0.075777,-0.47403 -0.028798,-0.14887 -0.2831807,-0.22565 -0.6457867,-0.22565 -0.3626265,0 -0.6169468,0.0768 -0.6457865,0.22565 -0.020408,0.10559 -0.053047,0.32285 -0.085459,0.53464 -0.043019,0.0282 -0.087115,0.055 -0.1267154,0.0876 H 5.8530383 c -0.010012,0.0164 -0.028546,0.0278 -0.049255,0.0278 H 5.2775555 c -0.020687,0 -0.03798,-0.0113 -0.047992,-0.0278 h -1.411553 c -0.054123,-0.045 -0.1129686,-0.0841 -0.1734445,-0.12082 -0.029054,-0.1895 -0.060403,-0.40239 -0.079566,-0.5014 -0.028798,-0.14887 -0.2836438,-0.22565 -0.6462072,-0.22565 z" + style="fill:#c6c7c9;fill-opacity:1;fill-rule:nonzero;stroke:#010101;stroke-width:0.12790291;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/controllers/joystick_generic.svg b/resources/graphics/controllers/joystick_generic.svg index b6b01c15a..a8e02278f 100644 --- a/resources/graphics/controllers/joystick_generic.svg +++ b/resources/graphics/controllers/joystick_generic.svg @@ -15,7 +15,7 @@ version="1.1" id="svg4925" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" - sodipodi:docname="unknown.svg"> + sodipodi:docname="joystick_generic.svg"> + id="g5030" + transform="matrix(1.0307912,0,0,1.0307912,-0.17108346,-8.8028203)"> + + + + + + + + + + + + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.04973952" + inkscape:connector-curvature="0" + id="path30" + d="m 4.1528229,289.19656 c 0,0.45263 -0.3680723,0.8207 -0.820702,0.8207 -0.4526295,0 -0.8207019,-0.36807 -0.8207019,-0.8207 0,-0.45263 0.3680724,-0.82071 0.8207019,-0.82071 0.4526297,0 0.820702,0.36808 0.820702,0.82071 z m -0.820702,-0.72123 c -0.397916,0 -0.7212229,0.32331 -0.7212229,0.72123 0,0.39792 0.3233069,0.72122 0.7212229,0.72122 0.3979161,0 0.7212231,-0.3233 0.7212231,-0.72122 0,-0.39792 -0.323307,-0.72123 -0.7212231,-0.72123 z" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/graphics/controllers/unknown.svg b/resources/graphics/controllers/unknown.svg index b6b01c15a..e7b99fea2 100644 --- a/resources/graphics/controllers/unknown.svg +++ b/resources/graphics/controllers/unknown.svg @@ -25,9 +25,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="6.7728148" - inkscape:cx="-21.346482" - inkscape:cy="-0.76992782" + inkscape:zoom="16.892814" + inkscape:cx="-37.558608" + inkscape:cy="23.448283" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" @@ -58,7 +58,7 @@ aria-label="?" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.72348785px;line-height:1.25;font-family:Digitalt;-inkscape-font-specification:Digitalt;letter-spacing:0px;word-spacing:0px;fill:#f00000;fill-opacity:1;stroke:#000000;stroke-width:0.2605817;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="text4825" - transform="matrix(0.91316098,0,0,0.91316098,1.9388092,25.308702)"> + transform="matrix(0.76718259,0,0,0.76718259,2.5170951,67.853186)"> Date: Sun, 24 Oct 2021 21:26:06 +0200 Subject: [PATCH 114/128] Improved the controller graphics for the 'c64' system. --- themes/rbsimple-DE/c64/images/controller.svg | 310 ++++++++++++++----- 1 file changed, 238 insertions(+), 72 deletions(-) diff --git a/themes/rbsimple-DE/c64/images/controller.svg b/themes/rbsimple-DE/c64/images/controller.svg index f38851465..536f68500 100644 --- a/themes/rbsimple-DE/c64/images/controller.svg +++ b/themes/rbsimple-DE/c64/images/controller.svg @@ -20,13 +20,36 @@ id="metadata43">image/svg+xml + id="defs41"> - - - - + + - - - - - - - - - - - - - - - - + - - \ No newline at end of file + style="fill:#cbc7b8;fill-opacity:1;stroke-width:0.77960926" /> \ No newline at end of file From 270a2e38578960875072d4ba74311eeda3f4f3af Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 25 Oct 2021 17:56:17 +0200 Subject: [PATCH 115/128] Added an option to preload gamelists on startup. --- es-app/src/guis/GuiMenu.cpp | 11 +++++++++++ es-app/src/views/ViewController.cpp | 6 +++++- es-app/src/views/gamelist/DetailedGameListView.h | 2 ++ es-app/src/views/gamelist/IGameListView.h | 2 ++ es-app/src/views/gamelist/VideoGameListView.h | 2 ++ es-core/src/Settings.cpp | 1 + 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 58bf31976..ebcfee71a 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -1046,6 +1046,17 @@ void GuiMenu::openOtherOptions() } }); + // Whether to preload the gamelists on application startup. + auto preloadGamelists = std::make_shared(mWindow); + preloadGamelists->setState(Settings::getInstance()->getBool("PreloadGamelists")); + s->addWithLabel("PRELOAD GAMELISTS ON STARTUP", preloadGamelists); + s->addSaveFunc([preloadGamelists, s] { + if (preloadGamelists->getState() != Settings::getInstance()->getBool("PreloadGamelists")) { + Settings::getInstance()->setBool("PreloadGamelists", preloadGamelists->getState()); + s->setNeedsSaving(); + } + }); + // Whether to enable alternative emulators per game (the option to disable this is intended // primarily for testing purposes). auto alternativeEmulatorPerGame = std::make_shared(mWindow); diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 5daa8fdc9..366c30a81 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -961,7 +961,11 @@ void ViewController::preload() std::to_string(systemCount) + ")"); } (*it)->getIndex()->resetFilters(); - getGameListView(*it); + + if (Settings::getInstance()->getBool("PreloadGamelists")) + getGameListView(*it)->preloadGamelist(); + else + getGameListView(*it); } // Load navigation sounds, either from the theme if it supports it, or otherwise from diff --git a/es-app/src/views/gamelist/DetailedGameListView.h b/es-app/src/views/gamelist/DetailedGameListView.h index ed4db80c2..08302d5a7 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.h +++ b/es-app/src/views/gamelist/DetailedGameListView.h @@ -25,6 +25,8 @@ public: virtual std::string getName() const override { return "detailed"; } virtual void launch(FileData* game) override; + virtual void preloadGamelist() override { updateInfoPanel(); } + protected: virtual void update(int deltaTime) override; diff --git a/es-app/src/views/gamelist/IGameListView.h b/es-app/src/views/gamelist/IGameListView.h index 853aa925e..212a062e9 100644 --- a/es-app/src/views/gamelist/IGameListView.h +++ b/es-app/src/views/gamelist/IGameListView.h @@ -32,6 +32,8 @@ public: void setTheme(const std::shared_ptr& theme); const std::shared_ptr& getTheme() const { return mTheme; } + virtual void preloadGamelist(){}; + virtual FileData* getCursor() = 0; virtual void setCursor(FileData*) = 0; virtual FileData* getNextEntry() = 0; diff --git a/es-app/src/views/gamelist/VideoGameListView.h b/es-app/src/views/gamelist/VideoGameListView.h index 06d1c1034..e0d6775de 100644 --- a/es-app/src/views/gamelist/VideoGameListView.h +++ b/es-app/src/views/gamelist/VideoGameListView.h @@ -28,6 +28,8 @@ public: virtual std::string getName() const override { return "video"; } virtual void launch(FileData* game) override; + virtual void preloadGamelist() override { updateInfoPanel(); } + protected: virtual void update(int deltaTime) override; diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index e7035633c..34c3a8c8b 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -242,6 +242,7 @@ void Settings::setDefaults() mBoolMap["VideoHardwareDecoding"] = {false, false}; #endif mBoolMap["VideoUpscaleFrameRate"] = {false, false}; + mBoolMap["PreloadGamelists"] = {true, true}; mBoolMap["AlternativeEmulatorPerGame"] = {true, true}; mBoolMap["ShowHiddenFiles"] = {true, true}; mBoolMap["ShowHiddenGames"] = {true, true}; From 7ed0267f5b05d6d12ad74379d594d01359563b98 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 25 Oct 2021 18:39:58 +0200 Subject: [PATCH 116/128] Made a large optimization to the SVG rasterization logic. --- es-core/src/Window.cpp | 2 +- es-core/src/components/ImageComponent.h | 3 +- es-core/src/components/NinePatchComponent.cpp | 2 +- es-core/src/components/OptionListComponent.h | 8 ++- es-core/src/components/SwitchComponent.cpp | 11 +-- es-core/src/components/SwitchComponent.h | 3 +- es-core/src/resources/TextureData.cpp | 67 ++++++++++++------- es-core/src/resources/TextureData.h | 7 ++ es-core/src/resources/TextureResource.cpp | 22 ++++-- es-core/src/resources/TextureResource.h | 6 ++ 10 files changed, 81 insertions(+), 50 deletions(-) diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 0943e8f04..629872054 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -600,8 +600,8 @@ void Window::renderLoadingScreen(std::string text) static_cast(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF); ImageComponent splash(this, true); - splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f); splash.setImage(":/graphics/splash.svg"); + splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f); splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x) / 2.0f, (Renderer::getScreenHeight() - splash.getSize().y) / 2.0f * 0.6f); splash.render(trans); diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 40b15cf3d..0f22d86a1 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -109,7 +109,8 @@ private: bool mTargetIsMin; // Calculates the correct mSize from our resizing information (set by setResize/setMaxSize). - // Used internally whenever the resizing parameters or texture change. + // Used internally whenever the resizing parameters or texture change. This function also + // initiates the SVG rasterization. void resize(); Renderer::Vertex mVertices[4]; diff --git a/es-core/src/components/NinePatchComponent.cpp b/es-core/src/components/NinePatchComponent.cpp index d908499fd..ad6eaab3f 100644 --- a/es-core/src/components/NinePatchComponent.cpp +++ b/es-core/src/components/NinePatchComponent.cpp @@ -59,7 +59,7 @@ void NinePatchComponent::buildVertices() else scaleFactor = glm::clamp(Renderer::getScreenWidthModifier(), 0.4f, 3.0f); - mTexture = TextureResource::get(mPath, false, false, true, true, scaleFactor); + mTexture = TextureResource::get(mPath, false, false, true, true, true, scaleFactor); if (mTexture->getSize() == glm::ivec2{}) { mVertices = nullptr; diff --git a/es-core/src/components/OptionListComponent.h b/es-core/src/components/OptionListComponent.h index c25812743..181b8693a 100644 --- a/es-core/src/components/OptionListComponent.h +++ b/es-core/src/components/OptionListComponent.h @@ -76,8 +76,12 @@ public: // Handles positioning/resizing of text and arrows. void onSizeChanged() override { - mLeftArrow.setResize(0, mText.getFont()->getLetterHeight()); - mRightArrow.setResize(0, mText.getFont()->getLetterHeight()); + if (mText.getFont()->getLetterHeight() != mLeftArrow.getSize().y || + mLeftArrow.getTexture()->getPendingRasterization()) + mLeftArrow.setResize(0, mText.getFont()->getLetterHeight()); + if (mText.getFont()->getLetterHeight() != mRightArrow.getSize().y || + mRightArrow.getTexture()->getPendingRasterization()) + mRightArrow.setResize(0, mText.getFont()->getLetterHeight()); if (mSize.x < (mLeftArrow.getSize().x + mRightArrow.getSize().x)) { LOG(LogWarning) << "OptionListComponent too narrow"; diff --git a/es-core/src/components/SwitchComponent.cpp b/es-core/src/components/SwitchComponent.cpp index a2712ec5d..e1249009a 100644 --- a/es-core/src/components/SwitchComponent.cpp +++ b/es-core/src/components/SwitchComponent.cpp @@ -49,16 +49,6 @@ void SwitchComponent::render(const glm::mat4& parentTrans) renderChildren(trans); } -void SwitchComponent::setResize(float width, float height) -{ - // Reposition the switch after resizing to make it centered. - const glm::vec2 oldSize = mImage.getSize(); - mImage.setResize(width, height); - const float xDiff = oldSize.x - mImage.getSize().x; - const float yDiff = oldSize.y - mImage.getSize().y; - mImage.setPosition(mImage.getPosition().x + xDiff, mImage.getPosition().y + yDiff / 2.0f); -} - void SwitchComponent::setState(bool state) { mState = state; @@ -81,6 +71,7 @@ void SwitchComponent::setValue(const std::string& statestring) void SwitchComponent::onStateChanged() { mImage.setImage(mState ? ":/graphics/on.svg" : ":/graphics/off.svg"); + mImage.setResize(mSize); // Change the color of the switch to reflect the changes. if (mState == mOriginalValue) diff --git a/es-core/src/components/SwitchComponent.h b/es-core/src/components/SwitchComponent.h index a0d1ac33c..47011b29d 100644 --- a/es-core/src/components/SwitchComponent.h +++ b/es-core/src/components/SwitchComponent.h @@ -22,8 +22,7 @@ public: void render(const glm::mat4& parentTrans) override; void onSizeChanged() override { mImage.setSize(mSize); } - void setResize(float width, float height) override; - + void setResize(float width, float height) override { setSize(width, height); } bool getState() const { return mState; } void setState(bool state); std::string getValue() const override; diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp index 9537b25d7..927a03854 100644 --- a/es-core/src/resources/TextureData.cpp +++ b/es-core/src/resources/TextureData.cpp @@ -24,16 +24,18 @@ #define DPI 96 TextureData::TextureData(bool tile) - : mTile(tile) - , mTextureID(0) + : mTile{tile} + , mTextureID{0} , mDataRGBA({}) - , mWidth(0) - , mHeight(0) - , mSourceWidth(0.0f) - , mSourceHeight(0.0f) - , mScaleDuringLoad(1.0f) - , mScalable(false) - , mLinearMagnify(false) + , mWidth{0} + , mHeight{0} + , mSourceWidth{0.0f} + , mSourceHeight{0.0f} + , mScaleDuringLoad{1.0f} + , mScalable{false} + , mLinearMagnify{false} + , mAlwaysRasterize{false} + , mPendingRasterization{false} { } @@ -54,21 +56,26 @@ void TextureData::initFromPath(const std::string& path) bool TextureData::initSVGFromMemory(const std::string& fileData) { // If already initialized then don't process it again. - std::unique_lock lock(mMutex); + std::unique_lock lock{mMutex}; if (!mDataRGBA.empty()) return true; - NSVGimage* svgImage = nsvgParse(const_cast(fileData.c_str()), "px", DPI); + NSVGimage* svgImage{nsvgParse(const_cast(fileData.c_str()), "px", DPI)}; if (!svgImage) { LOG(LogError) << "Couldn't parse SVG image"; return false; } - // We want to rasterize this texture at a specific resolution. If the source size - // variables are set then use them, otherwise get them from the parsed file. - if ((mSourceWidth == 0.0f) && (mSourceHeight == 0.0f)) { + bool rasterize{true}; + + // If there is no image size defined yet, then don't rasterize unless mAlwaysRasterize has + // been set (this is only used by NinePatchComponent to avoid flickering menus). + if (mSourceWidth == 0.0f && mSourceHeight == 0.0f) { + if (!mAlwaysRasterize) + rasterize = false; + mSourceWidth = svgImage->width; mSourceHeight = svgImage->height; } @@ -87,23 +94,31 @@ bool TextureData::initSVGFromMemory(const std::string& fileData) std::round((static_cast(mWidth) / svgImage->width) * svgImage->height)); } - std::vector tempVector; - tempVector.reserve(mWidth * mHeight * 4); + if (rasterize) { + std::vector tempVector; + tempVector.reserve(mWidth * mHeight * 4); - NSVGrasterizer* rast = nsvgCreateRasterizer(); + NSVGrasterizer* rast = nsvgCreateRasterizer(); - nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), mWidth, - mHeight, mWidth * 4); + nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), mWidth, + mHeight, mWidth * 4); + + nsvgDeleteRasterizer(rast); + + mDataRGBA.insert(mDataRGBA.begin(), tempVector.data(), + tempVector.data() + (mWidth * mHeight * 4)); + + ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight); + mPendingRasterization = false; + } + else { + // TODO: Fix this properly instead of using the single byte texture workaround. + mDataRGBA.push_back(0); + mPendingRasterization = true; + } - // This is important in order to avoid memory leaks. - nsvgDeleteRasterizer(rast); nsvgDelete(svgImage); - mDataRGBA.insert(mDataRGBA.begin(), tempVector.data(), - tempVector.data() + (mWidth * mHeight * 4)); - - ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight); - return true; } diff --git a/es-core/src/resources/TextureData.h b/es-core/src/resources/TextureData.h index 0e76d8159..16394167e 100644 --- a/es-core/src/resources/TextureData.h +++ b/es-core/src/resources/TextureData.h @@ -61,6 +61,11 @@ public: void setScaleDuringLoad(float scale) { mScaleDuringLoad = scale; } // Whether to use linear filtering when magnifying the texture. void setLinearMagnify(bool setting) { mLinearMagnify = setting; } + // Whether to rasterize the image even if a size has not been set yet. + void setAlwaysRasterize(bool setting) { mAlwaysRasterize = setting; } + + // Has the image been loaded but not yet been rasterized as the size was not known? + bool getPendingRasterization() { return mPendingRasterization; } std::vector getRawRGBAData() { return mDataRGBA; } std::string getTextureFilePath() { return mPath; } @@ -80,6 +85,8 @@ private: bool mScalable; bool mLinearMagnify; bool mReloadable; + bool mAlwaysRasterize; + bool mPendingRasterization; }; #endif // ES_CORE_RESOURCES_TEXTURE_DATA_H diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index b6f7b8d8a..0b2c3820d 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -8,16 +8,20 @@ #include "resources/TextureResource.h" -#include "resources/TextureData.h" #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" TextureDataManager TextureResource::sTextureDataManager; std::map> TextureResource::sTextureMap; std::set TextureResource::sAllTextures; -TextureResource::TextureResource( - const std::string& path, bool tile, bool dynamic, bool linearMagnify, float scaleDuringLoad) +TextureResource::TextureResource(const std::string& path, + bool tile, + bool dynamic, + bool linearMagnify, + bool alwaysRasterize, + float scaleDuringLoad) : mTextureData(nullptr) , mForceLoad(false) { @@ -32,6 +36,7 @@ TextureResource::TextureResource( if (scaleDuringLoad != 1.0f) data->setScaleDuringLoad(scaleDuringLoad); data->setLinearMagnify(linearMagnify); + data->setAlwaysRasterize(alwaysRasterize); // Force the texture manager to load it using a blocking load. sTextureDataManager.load(data, true); } @@ -42,6 +47,7 @@ TextureResource::TextureResource( if (scaleDuringLoad != 1.0f) data->setScaleDuringLoad(scaleDuringLoad); data->setLinearMagnify(linearMagnify); + data->setAlwaysRasterize(alwaysRasterize); // Load it so we can read the width/height. data->load(); } @@ -148,6 +154,7 @@ std::shared_ptr TextureResource::get(const std::string& path, bool forceLoad, bool dynamic, bool linearMagnify, + bool alwaysRasterize, float scaleDuringLoad) { std::shared_ptr& rm = ResourceManager::getInstance(); @@ -155,7 +162,7 @@ std::shared_ptr TextureResource::get(const std::string& path, const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(path); if (canonicalPath.empty()) { std::shared_ptr tex( - new TextureResource("", tile, false, linearMagnify, scaleDuringLoad)); + new TextureResource("", tile, false, linearMagnify, alwaysRasterize, scaleDuringLoad)); // Make sure we get properly deinitialized even though we do nothing on reinitialization. rm->addReloadable(tex); return tex; @@ -171,12 +178,13 @@ std::shared_ptr TextureResource::get(const std::string& path, // Need to create it. std::shared_ptr tex; - tex = std::shared_ptr( - new TextureResource(key.first, tile, dynamic, linearMagnify, scaleDuringLoad)); + tex = std::shared_ptr(new TextureResource( + key.first, tile, dynamic, linearMagnify, alwaysRasterize, scaleDuringLoad)); std::shared_ptr data = sTextureDataManager.get(tex.get()); // Is it an SVG? - if (key.first.substr(key.first.size() - 4, std::string::npos) != ".svg") { + if (Utils::String::toLower(key.first.substr(key.first.size() - 4, std::string::npos)) != + ".svg") { // Probably not. Add it to our map. We don't add SVGs because 2 SVGs might be // rasterized at different sizes. sTextureMap[key] = std::weak_ptr(tex); diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 91ba5ef7e..9e5f02a2f 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -10,6 +10,7 @@ #define ES_CORE_RESOURCES_TEXTURE_RESOURCE_H #include "resources/ResourceManager.h" +#include "resources/TextureData.h" #include "resources/TextureDataManager.h" #include "utils/MathUtil.h" @@ -30,6 +31,7 @@ public: bool forceLoad = false, bool dynamic = true, bool linearMagnify = false, + bool alwaysRasterize = false, float scaleDuringLoad = 1.0f); void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height); virtual void initFromMemory(const char* data, size_t length); @@ -38,6 +40,9 @@ public: // Returns the raw pixel values. std::vector getRawRGBAData(); + // Has the image been loaded but not yet been rasterized as the size was not known? + bool getPendingRasterization() { return mTextureData->getPendingRasterization(); } + std::string getTextureFilePath(); // For SVG graphics this function effectively rescales the image to the defined size. @@ -65,6 +70,7 @@ protected: bool tile, bool dynamic, bool linearMagnify, + bool alwaysRasterize, float scaleDuringLoad); virtual void unload(std::shared_ptr& rm); virtual void reload(std::shared_ptr& rm); From e149fea3fbe25d5bd29659c8365d85094351e7d4 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 25 Oct 2021 18:42:09 +0200 Subject: [PATCH 117/128] Small adjustment to the 'completed' badge graphics. --- resources/graphics/badge_completed.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/graphics/badge_completed.svg b/resources/graphics/badge_completed.svg index 376d4f21b..941c24cc5 100644 --- a/resources/graphics/badge_completed.svg +++ b/resources/graphics/badge_completed.svg @@ -33,9 +33,9 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - inkscape:zoom="9.6754505" - inkscape:cx="14.68785" - inkscape:cy="65.424484" + inkscape:zoom="3.0165741" + inkscape:cx="-308.2567" + inkscape:cy="78.979511" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -143,7 +143,7 @@ style="fill:#f0f0f0;fill-opacity:1" /> From 4f6d2c7faed81de5b6e6e005bdb1f9d2b5d581d0 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 25 Oct 2021 18:45:18 +0200 Subject: [PATCH 118/128] (rbsimple-DE) Minor adjustments to the 'c64' system controller graphics. --- themes/rbsimple-DE/c64/images/controller.svg | 457 ++++++++++--------- 1 file changed, 243 insertions(+), 214 deletions(-) diff --git a/themes/rbsimple-DE/c64/images/controller.svg b/themes/rbsimple-DE/c64/images/controller.svg index 536f68500..c30aa519d 100644 --- a/themes/rbsimple-DE/c64/images/controller.svg +++ b/themes/rbsimple-DE/c64/images/controller.svg @@ -19,7 +19,7 @@ inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">image/svg+xml - - - + + \ No newline at end of file + transform="matrix(0.61238028,0,0,0.61238028,90.172607,35.147976)" + id="g8-69-2"> \ No newline at end of file From f32c3dc6f48f92520f9834d58d2a9f7f1c23ca91 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 25 Oct 2021 19:13:54 +0200 Subject: [PATCH 119/128] Reintroduced column mode for BadgeComponent. Also fixed an issue with direction in FlexboxComponent when using column mode. --- es-core/src/ThemeData.cpp | 5 +-- es-core/src/components/BadgeComponent.cpp | 37 +++++++++++++-------- es-core/src/components/FlexboxComponent.cpp | 34 +++++++++++++------ es-core/src/components/FlexboxComponent.h | 16 ++++----- themes/rbsimple-DE/theme.xml | 5 +-- 5 files changed, 61 insertions(+), 36 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index e1e83f432..2a1c7f07f 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -153,8 +153,9 @@ std::map> The {"rotation", FLOAT}, {"rotationOrigin", NORMALIZED_PAIR}, {"alignment", STRING}, - {"itemsPerRow", FLOAT}, - {"rows", FLOAT}, + {"direction", STRING}, + {"lines", FLOAT}, + {"itemsPerLine", FLOAT}, {"itemMargin", NORMALIZED_PAIR}, {"slots", STRING}, {"controllerPos", NORMALIZED_PAIR}, diff --git a/es-core/src/components/BadgeComponent.cpp b/es-core/src/components/BadgeComponent.cpp index 5402b62a9..3683b94b3 100644 --- a/es-core/src/components/BadgeComponent.cpp +++ b/es-core/src/components/BadgeComponent.cpp @@ -187,26 +187,37 @@ void BadgeComponent::applyTheme(const std::shared_ptr& theme, } } - if (elem->has("itemsPerRow")) { - const float itemsPerRow{elem->get("itemsPerRow")}; - if (itemsPerRow < 1.0f || itemsPerRow > 10.0f) { - LOG(LogWarning) - << "BadgeComponent: Invalid theme configuration, set to \"" - << itemsPerRow << "\""; + if (elem->has("direction")) { + const std::string direction{elem->get("direction")}; + if (direction != "row" && direction != "column") { + LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" + << direction << "\""; } else { - mFlexboxComponent.setItemsPerLine(static_cast(itemsPerRow)); + mFlexboxComponent.setDirection(direction); } } - if (elem->has("rows")) { - const float rows{elem->get("rows")}; - if (rows < 1.0f || rows > 10.0f) { - LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" - << rows << "\""; + if (elem->has("lines")) { + const float lines{elem->get("lines")}; + if (lines < 1.0f || lines > 10.0f) { + LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" + << lines << "\""; } else { - mFlexboxComponent.setLines(static_cast(rows)); + mFlexboxComponent.setLines(static_cast(lines)); + } + } + + if (elem->has("itemsPerLine")) { + const float itemsPerLine{elem->get("itemsPerLine")}; + if (itemsPerLine < 1.0f || itemsPerLine > 10.0f) { + LOG(LogWarning) + << "BadgeComponent: Invalid theme configuration, set to \"" + << itemsPerLine << "\""; + } + else { + mFlexboxComponent.setItemsPerLine(static_cast(itemsPerLine)); } } diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index da3bf1b1c..3b9fe9a0d 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -24,8 +24,8 @@ FlexboxComponent::FlexboxComponent(Window* window, std::vector& ite , mItems(items) , mDirection{DEFAULT_DIRECTION} , mAlignment{DEFAULT_ALIGNMENT} - , mItemsPerLine{DEFAULT_ITEMS_PER_LINE} , mLines{DEFAULT_LINES} + , mItemsPerLine{DEFAULT_ITEMS_PER_LINE} , mItemPlacement{DEFAULT_ITEM_PLACEMENT} , mItemMargin{glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}} , mOverlayPosition{0.5f, 0.5f} @@ -86,8 +86,6 @@ void FlexboxComponent::setItemMargin(glm::vec2 value) void FlexboxComponent::computeLayout() { - // TODO: There is no right-alignment support for column mode. - // If we're not clamping itemMargin to a reasonable value, all kinds of weird rendering // issues could occur. mItemMargin.x = glm::clamp(mItemMargin.x, 0.0f, mSize.x / 2.0f); @@ -107,7 +105,13 @@ void FlexboxComponent::computeLayout() mItemsPerLine = static_cast(mItems.size()); } - glm::vec2 grid{mItemsPerLine, mLines}; + glm::vec2 grid{}; + + if (mDirection == "row") + grid = {mItemsPerLine, mLines}; + else + grid = {mLines, mItemsPerLine}; + glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid}; float rowHeight{0.0f}; @@ -146,11 +150,11 @@ void FlexboxComponent::computeLayout() maxItemSize = glm::round(maxItemSize); - bool alignRight{mAlignment == "right" && mDirection == "row"}; + bool alignRight{mAlignment == "right"}; float alignRightComp{0.0f}; // If right-aligning, move the overall container contents during grid setup. - if (alignRight) + if (alignRight && mDirection == "row") alignRightComp = std::round(mSize.x - ((maxItemSize.x + mItemMargin.x) * grid.x) + mItemMargin.x); @@ -166,11 +170,19 @@ void FlexboxComponent::computeLayout() } } } - else { // Column mode. + else if (mDirection == "column" && !alignRight) { + for (int x = 0; x < grid.x; x++) { + for (int y = 0; y < grid.y; y++) { + itemPositions.push_back(glm::vec2{(x * (maxItemSize.x + mItemMargin.x)), + y * (rowHeight + mItemMargin.y)}); + } + } + } + else { // Right-aligned. for (int x = 0; x < grid.x; x++) { for (int y = 0; y < grid.y; y++) { itemPositions.push_back( - glm::vec2{(x * (maxItemSize.x + mItemMargin.x) + alignRightComp), + glm::vec2{(mSize.x - (x * (maxItemSize.x + mItemMargin.x)) - maxItemSize.x), y * (rowHeight + mItemMargin.y)}); } } @@ -185,7 +197,7 @@ void FlexboxComponent::computeLayout() if (!item.visible) continue; - if (pos > 0) { + if (mDirection == "row" && pos > 0) { if (itemPositions[pos - 1].y < itemPositions[pos].y) { lastY = itemPositions[pos].y; itemsOnLastRow = 0; @@ -225,8 +237,8 @@ void FlexboxComponent::computeLayout() pos++; } - // Apply right-align to the items (only works in row mode). - if (alignRight) { + // Apply right-align to the items if we're using row mode. + if (alignRight && mDirection == "row") { for (auto& item : mItems) { if (!item.visible) continue; diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index 4ab5ee5c3..ce5a6c60f 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -44,13 +44,6 @@ public: mLayoutValid = false; } - unsigned int getItemsPerLine() const { return mItemsPerLine; } - void setItemsPerLine(unsigned int value) - { - mItemsPerLine = value; - mLayoutValid = false; - } - unsigned int getLines() const { return mLines; } void setLines(unsigned int value) { @@ -58,6 +51,13 @@ public: mLayoutValid = false; } + unsigned int getItemsPerLine() const { return mItemsPerLine; } + void setItemsPerLine(unsigned int value) + { + mItemsPerLine = value; + mLayoutValid = false; + } + std::string getItemPlacement() const { return mItemPlacement; } void setItemPlacement(const std::string& value) { @@ -87,8 +87,8 @@ private: // Layout options. std::string mDirection; std::string mAlignment; - unsigned int mItemsPerLine; unsigned int mLines; + unsigned int mItemsPerLine; std::string mItemPlacement; glm::vec2 mItemMargin; diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 7d543fe59..4d59d93e2 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -241,8 +241,9 @@ based on: 'recalbox-multi' by the Recalbox community 0.13 0.1635 0.5 0.5 left - 3 - 2 + row + 2 + 3 0.5 0.572 0.81 -1.0 0.005 From be74fd26ef4e0ffdb0ea088bc43bbfe69827f98a Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 25 Oct 2021 19:15:37 +0200 Subject: [PATCH 120/128] Small adjustments to the generic joystick controller icon. --- .../graphics/controllers/joystick_generic.svg | 398 ++++++++++-------- 1 file changed, 212 insertions(+), 186 deletions(-) diff --git a/resources/graphics/controllers/joystick_generic.svg b/resources/graphics/controllers/joystick_generic.svg index a8e02278f..ebabd7953 100644 --- a/resources/graphics/controllers/joystick_generic.svg +++ b/resources/graphics/controllers/joystick_generic.svg @@ -25,9 +25,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="22.627417" - inkscape:cx="-1.2394882" - inkscape:cy="24.852525" + inkscape:zoom="28.21875" + inkscape:cx="6.1924519" + inkscape:cy="24.290783" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" @@ -55,81 +55,80 @@ id="layer1" transform="translate(0,-285.88748)"> + id="g4868"> + style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.00956567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.02827883;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="opacity:1;vector-effect:none;fill:#161616;fill-opacity:1;stroke:none;stroke-width:0.02487354;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="opacity:1;vector-effect:none;fill:#161616;fill-opacity:1;stroke:none;stroke-width:0.03217189;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="opacity:1;vector-effect:none;fill:#161616;fill-opacity:1;stroke:none;stroke-width:0.02511485;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="opacity:1;vector-effect:none;fill:#161616;fill-opacity:1;stroke:none;stroke-width:0.02944537;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="opacity:1;vector-effect:none;fill:#333333;fill-opacity:1;stroke:none;stroke-width:0.03223006;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="opacity:1;vector-effect:none;fill:#0c0c0c;fill-opacity:1;stroke:#0a0a0a;stroke-width:0.08007559;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - + d="m 4.1096097,289.29845 c 0,0.46657 -0.3794056,0.84597 -0.8459724,0.84597 -0.4665665,0 -0.8459723,-0.3794 -0.8459723,-0.84597 0,-0.46657 0.3794058,-0.84598 0.8459723,-0.84598 0.4665668,0 0.8459724,0.37941 0.8459724,0.84598 z m -0.8459724,-0.74344 c -0.4101683,0 -0.7434302,0.33327 -0.7434302,0.74344 0,0.41017 0.3332619,0.74343 0.7434302,0.74343 0.4101685,0 0.7434305,-0.33326 0.7434305,-0.74343 0,-0.41017 -0.333262,-0.74344 -0.7434305,-0.74344 z" /> + d="m 8.6954757,289.29845 c 0,0.46657 -0.3794058,0.84597 -0.8459722,0.84597 -0.4665662,0 -0.8459718,-0.3794 -0.8459718,-0.84597 0,-0.46657 0.3794056,-0.84598 0.8459718,-0.84598 0.4665664,0 0.8459722,0.37941 0.8459722,0.84598 z m -0.8459722,-0.74344 c -0.4101681,0 -0.7434299,0.33327 -0.7434299,0.74344 0,0.41016 0.3332618,0.74343 0.7434299,0.74343 0.4101688,0 0.7434306,-0.33327 0.7434306,-0.74343 0,-0.41017 -0.3332618,-0.74344 -0.7434306,-0.74344 z" /> + + + + + + + + + + + + + + + + + + + + + + + + + + From c0f37cd088da929d4c51d5fe5b3e9d891f8dac60 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 25 Oct 2021 19:17:40 +0200 Subject: [PATCH 121/128] Documentation update. --- CHANGELOG.md | 4 +++- CONTRIBUTING.md | 4 ++-- THEMES-DEV.md | 12 +++++++----- USERGUIDE-DEV.md | 4 ++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1322caa21..af4d4a9f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,14 +23,16 @@ * Improved the gamelist filter screen to not allow filtering of values where there is no actual data to filter, e.g. Favorites for a system with no favorite games * Grayed out all fields in the gamelist filter screen where there is no data to filter, previously some fields were removed entirely and some could still be used * Added the ability to filter on blank/unknown values for Genre, Player, Developer, Publisher and Alternative emulator. -* Added a filter for "Alternative emulator" and sorted the filters in the same order as the metadata editor fields +* Added filters for "Alternative emulator" and "Controller badges" and sorted the filters in the same order as the metadata editor fields * Added a menu option to change the application exit key combination +* Added an option to preload the gamelists on startup which leads to smoother navigation when first entering each gamelist * Lowered the minimum supported screen resolution from 640x480 to 224x224 to support arcade cabinet displays such as those running at 384x224 and 224x384 * Expanded the themeable options for "helpsystem" to support custom button graphics, dimmed text and icon colors, upper/lower/camel case and custom spacing * Made the scrolling speed of ScrollableContainer more consistent across various screen resolutions and display aspect ratios * Decreased the amount of text that ScrollableContainer renders above and below the starting position as content is scrolled * Made the game name and description stop scrolling when running the media viewer, the screensaver or when running in the background while a game is launched * Added notification popups when plugging in or removing controllers +* Made large optimizations to the SVG rendering which reduces application startup time dramatically when many systems are populated * Changed to loading the default theme set rbsimple-DE instead of the first available theme if the currently configured theme is missing * Added support for using the left and right trigger buttons in the help prompts * Removed the "Choose" entry from the help prompts in the gamelist view diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e35e752ba..15f31d09b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,8 +47,8 @@ This plan is under constant review so expect it to change from time to time. Sti #### v1.4 +* Bulk metadata editor * Localization/multi-language support -* Reorganize the menus, possibly adding basic/advanced modes * Authoring tools to clean up orphaned gamelist entries, media files etc. * Add scraping of game manuals and maps and create a viewer for these (with PDF, GIF, JPG and PNG support) * Scrollbar component for the gamelist view which can be used by the themes @@ -58,7 +58,7 @@ This plan is under constant review so expect it to change from time to time. Sti #### v1.5 -* Bulk metadata editor +* Reorganize the menus, possibly adding basic/advanced modes * Simple file browsing component * Improve the performance of the GLSL shader code * Animated menu elements like switches, tick boxes, smooth scrolling etc. diff --git a/THEMES-DEV.md b/THEMES-DEV.md index 6d078639e..9e9e1815c 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -910,7 +910,7 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren #### badges -It's strongly recommended to use the same image dimensions for all badges as varying aspect ratios will lead to alignment issues. For the controller images it's recommended to keep to the square canvas size used by the default bundled graphics as otherwise sizing and placement will be inconsistent (unless all controller graphic files are customized of course). Overall it's a very good idea to keep the image dimensions small. This is especially true for SVG graphics as rasterization will otherwise take a long time which will slow down application startup and gamelist navigation. +It's strongly recommended to use the same image dimensions for all badges as varying aspect ratios will lead to alignment issues. For the controller images it's recommended to keep to the square canvas size used by the default bundled graphics as otherwise sizing and placement will be inconsistent (unless all controller graphic files are customized of course). * `pos` - type: NORMALIZED_PAIR. * `size` - type: NORMALIZED_PAIR. @@ -922,10 +922,12 @@ It's strongly recommended to use the same image dimensions for all badges as var - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Default is `0`. * `rotationOrigin` - type: NORMALIZED_PAIR. - Point around which the image will be rotated. Default is `0.5 0.5`. -* `itemsPerRow` - type: FLOAT. - - Number of badges that fit on a row. When more badges are available a new row will be started. Default is `4`. -* `rows` - type: FLOAT. - - The number of rows available. Default is `2`. +* `direction` - type: STRING. + - Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. Default is `row`. +* `lines` - type: FLOAT. + - The number of lines available. Default is `2`. +* `itemsPerLine` - type: FLOAT. + - Number of badges that fit on a line. When more badges are available a new line will be started. Default is `4`. * `itemMargin` - type: NORMALIZED_PAIR. - The margins between badges. Possible combinations: - `x y` - horizontal and vertical margins. Minimum value per axis is `0`, maximum value is `0.2`. Default is `0.01 0.01`. If one of the axis is set to `-1` the margin of the other axis (in pixels) will be used, which makes it possible to get identical spacing between all items regardless of screen aspect ratio. diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 8669a463e..d56306a2a 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -1252,6 +1252,10 @@ Enabling this option offloads video decoding to the GPU. Whether this actually i With this option enabled, videos with lower frame rates than 60 FPS, such as 24 and 30 will get upscaled to 60 FPS. This results in slightly smoother playback for some videos. There is a small performance hit from this option, so on weaker machines it may be necessary to disable it for fluent video playback. This setting has no effect when using the VLC video player. If the VLC video player is not included in the ES-DE build, the "(FFmpeg)" text is omitted from the setting name. +**Preload gamelists on startup** + +When this option is enabled, all gamelists will be loaded on application startup. This will increase the startup time slightly and lead to a higher initial memory utilization, but navigation will be smoother the first time a gamelist is entered. The improvement is especially noticeable when the _slide_ transition style has been selected. + **Enable alternative emulators per game** If enabled, you will be able to select alternative emulators per game using the metadata editor. It's only recommended to disable this option for testing purposes. From 848e0a1a94e828e5f25da9152c5dc9741fc7a470 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 26 Oct 2021 18:13:31 +0200 Subject: [PATCH 122/128] Fixed an SVG graphics corruption issue. --- es-core/src/resources/TextureData.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp index 927a03854..e8476fbd8 100644 --- a/es-core/src/resources/TextureData.cpp +++ b/es-core/src/resources/TextureData.cpp @@ -75,9 +75,9 @@ bool TextureData::initSVGFromMemory(const std::string& fileData) if (mSourceWidth == 0.0f && mSourceHeight == 0.0f) { if (!mAlwaysRasterize) rasterize = false; - - mSourceWidth = svgImage->width; - mSourceHeight = svgImage->height; + // Set a small temporary size that maintains the image aspect ratio. + mSourceWidth = 64.0f; + mSourceHeight = 64.0f * (svgImage->height / svgImage->width); } mWidth = static_cast(std::round(mSourceWidth * mScaleDuringLoad)); From 3d3d9518422b632727fb3dd64189ac501eb22ca5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 26 Oct 2021 18:22:41 +0200 Subject: [PATCH 123/128] Renamed alwaysRasterize to forceRasterization. Also removed an unnecessary function call in ImageComponent. --- es-core/src/components/ImageComponent.cpp | 4 ++-- es-core/src/resources/TextureData.cpp | 6 +++--- es-core/src/resources/TextureData.h | 4 ++-- es-core/src/resources/TextureResource.cpp | 16 ++++++++-------- es-core/src/resources/TextureResource.h | 10 ++++++---- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 4055c9989..92aa7c7d7 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -333,7 +333,7 @@ void ImageComponent::setSaturation(float saturation) void ImageComponent::updateVertices() { - if (!mTexture || !mTexture->isInitialized()) + if (!mTexture) return; // We go through this mess to make sure everything is properly rounded. @@ -398,7 +398,7 @@ void ImageComponent::render(const glm::mat4& parentTrans) Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033); } // An image with zero size would normally indicate a corrupt image file. - if (mTexture->isInitialized() && mTexture->getSize() != glm::ivec2{}) { + if (mTexture->getSize() != glm::ivec2{}) { // Actually draw the image. // The bind() function returns false if the texture is not currently loaded. A blank // texture is bound in this case but we want to handle a fade so it doesn't just diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp index e8476fbd8..e9c05e198 100644 --- a/es-core/src/resources/TextureData.cpp +++ b/es-core/src/resources/TextureData.cpp @@ -34,7 +34,7 @@ TextureData::TextureData(bool tile) , mScaleDuringLoad{1.0f} , mScalable{false} , mLinearMagnify{false} - , mAlwaysRasterize{false} + , mForceRasterization{false} , mPendingRasterization{false} { } @@ -70,10 +70,10 @@ bool TextureData::initSVGFromMemory(const std::string& fileData) bool rasterize{true}; - // If there is no image size defined yet, then don't rasterize unless mAlwaysRasterize has + // If there is no image size defined yet, then don't rasterize unless mForceRasterization has // been set (this is only used by NinePatchComponent to avoid flickering menus). if (mSourceWidth == 0.0f && mSourceHeight == 0.0f) { - if (!mAlwaysRasterize) + if (!mForceRasterization) rasterize = false; // Set a small temporary size that maintains the image aspect ratio. mSourceWidth = 64.0f; diff --git a/es-core/src/resources/TextureData.h b/es-core/src/resources/TextureData.h index 16394167e..4fe3eb827 100644 --- a/es-core/src/resources/TextureData.h +++ b/es-core/src/resources/TextureData.h @@ -62,7 +62,7 @@ public: // Whether to use linear filtering when magnifying the texture. void setLinearMagnify(bool setting) { mLinearMagnify = setting; } // Whether to rasterize the image even if a size has not been set yet. - void setAlwaysRasterize(bool setting) { mAlwaysRasterize = setting; } + void setForceRasterization(bool setting) { mForceRasterization = setting; } // Has the image been loaded but not yet been rasterized as the size was not known? bool getPendingRasterization() { return mPendingRasterization; } @@ -85,7 +85,7 @@ private: bool mScalable; bool mLinearMagnify; bool mReloadable; - bool mAlwaysRasterize; + bool mForceRasterization; bool mPendingRasterization; }; diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index 0b2c3820d..8217a9f58 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -20,7 +20,7 @@ TextureResource::TextureResource(const std::string& path, bool tile, bool dynamic, bool linearMagnify, - bool alwaysRasterize, + bool forceRasterization, float scaleDuringLoad) : mTextureData(nullptr) , mForceLoad(false) @@ -36,7 +36,7 @@ TextureResource::TextureResource(const std::string& path, if (scaleDuringLoad != 1.0f) data->setScaleDuringLoad(scaleDuringLoad); data->setLinearMagnify(linearMagnify); - data->setAlwaysRasterize(alwaysRasterize); + data->setForceRasterization(forceRasterization); // Force the texture manager to load it using a blocking load. sTextureDataManager.load(data, true); } @@ -47,7 +47,7 @@ TextureResource::TextureResource(const std::string& path, if (scaleDuringLoad != 1.0f) data->setScaleDuringLoad(scaleDuringLoad); data->setLinearMagnify(linearMagnify); - data->setAlwaysRasterize(alwaysRasterize); + data->setForceRasterization(forceRasterization); // Load it so we can read the width/height. data->load(); } @@ -154,15 +154,15 @@ std::shared_ptr TextureResource::get(const std::string& path, bool forceLoad, bool dynamic, bool linearMagnify, - bool alwaysRasterize, + bool forceRasterization, float scaleDuringLoad) { std::shared_ptr& rm = ResourceManager::getInstance(); const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(path); if (canonicalPath.empty()) { - std::shared_ptr tex( - new TextureResource("", tile, false, linearMagnify, alwaysRasterize, scaleDuringLoad)); + std::shared_ptr tex(new TextureResource( + "", tile, false, linearMagnify, forceRasterization, scaleDuringLoad)); // Make sure we get properly deinitialized even though we do nothing on reinitialization. rm->addReloadable(tex); return tex; @@ -179,7 +179,7 @@ std::shared_ptr TextureResource::get(const std::string& path, // Need to create it. std::shared_ptr tex; tex = std::shared_ptr(new TextureResource( - key.first, tile, dynamic, linearMagnify, alwaysRasterize, scaleDuringLoad)); + key.first, tile, dynamic, linearMagnify, forceRasterization, scaleDuringLoad)); std::shared_ptr data = sTextureDataManager.get(tex.get()); // Is it an SVG? @@ -217,7 +217,7 @@ void TextureResource::rasterizeAt(float width, float height) data = sTextureDataManager.get(this); mSourceSize = glm::vec2{static_cast(width), static_cast(height)}; data->setSourceSize(static_cast(width), static_cast(height)); - if (mForceLoad || (mTextureData != nullptr)) + if (mForceLoad || mTextureData != nullptr) data->load(); } diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 9e5f02a2f..7ae64728b 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -31,7 +31,7 @@ public: bool forceLoad = false, bool dynamic = true, bool linearMagnify = false, - bool alwaysRasterize = false, + bool forceRasterization = false, float scaleDuringLoad = 1.0f); void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height); virtual void initFromMemory(const char* data, size_t length); @@ -41,7 +41,10 @@ public: std::vector getRawRGBAData(); // Has the image been loaded but not yet been rasterized as the size was not known? - bool getPendingRasterization() { return mTextureData->getPendingRasterization(); } + bool getPendingRasterization() + { + return (mTextureData != nullptr ? mTextureData->getPendingRasterization() : false); + } std::string getTextureFilePath(); @@ -54,7 +57,6 @@ public: virtual ~TextureResource(); - bool isInitialized() const { return true; } bool isTiled() const; const glm::ivec2 getSize() const { return mSize; } @@ -70,7 +72,7 @@ protected: bool tile, bool dynamic, bool linearMagnify, - bool alwaysRasterize, + bool forceRasterization, float scaleDuringLoad); virtual void unload(std::shared_ptr& rm); virtual void reload(std::shared_ptr& rm); From 58345863b3e9d9edbebd8794c86d09a139135217 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 26 Oct 2021 18:23:52 +0200 Subject: [PATCH 124/128] Changed an RGBA vector getter from copy to reference to reduce CPU usage. --- es-core/src/resources/TextureData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/resources/TextureData.h b/es-core/src/resources/TextureData.h index 4fe3eb827..1717bf101 100644 --- a/es-core/src/resources/TextureData.h +++ b/es-core/src/resources/TextureData.h @@ -67,7 +67,7 @@ public: // Has the image been loaded but not yet been rasterized as the size was not known? bool getPendingRasterization() { return mPendingRasterization; } - std::vector getRawRGBAData() { return mDataRGBA; } + std::vector& getRawRGBAData() { return mDataRGBA; } std::string getTextureFilePath() { return mPath; } bool tiled() { return mTile; } From 4f9737cd23bf38c6c6d6f8a7f72ada354749b982 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 26 Oct 2021 18:26:00 +0200 Subject: [PATCH 125/128] Fixed a compiler warning when building with the GLES renderer. --- es-core/src/Window.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 629872054..39c419136 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -24,7 +24,9 @@ #include #include +#if defined(USE_OPENGL_21) #define CLOCK_BACKGROUND_CREATION false +#endif Window::Window() : mScreensaver(nullptr) From 4e02ce230b16338eadd910166a91f9a6d12fc341 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 26 Oct 2021 18:27:30 +0200 Subject: [PATCH 126/128] Disabled dynamic loading of images in BadgeComponent. --- es-core/src/components/BadgeComponent.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/es-core/src/components/BadgeComponent.cpp b/es-core/src/components/BadgeComponent.cpp index 3683b94b3..abe74f0d7 100644 --- a/es-core/src/components/BadgeComponent.cpp +++ b/es-core/src/components/BadgeComponent.cpp @@ -277,8 +277,7 @@ void BadgeComponent::applyTheme(const std::shared_ptr& theme, FlexboxComponent::FlexboxItem item; item.label = slot; - ImageComponent badgeImage{mWindow}; - badgeImage.setForceLoad(true); + ImageComponent badgeImage{mWindow, false, false}; badgeImage.setImage(mBadgeIcons[slot]); item.baseImage = badgeImage; item.overlayImage = ImageComponent{mWindow}; From fbb974de0316eb8b5a247fad3278a1354be8d4fb Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 26 Oct 2021 18:28:21 +0200 Subject: [PATCH 127/128] Removed an unnecessary force load setter from ImageComponent. --- es-core/src/components/ImageComponent.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 0f22d86a1..e91955204 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -30,8 +30,6 @@ public: // Use an already existing texture. void setImage(const std::shared_ptr& texture, bool resizeTexture = true); - void setForceLoad(bool status) { mForceLoad = status; } - void onSizeChanged() override { updateVertices(); } // Resize the image to fit this size. If one axis is zero, scale that axis to maintain From a979c6d0e599f5333ea41a65df358258a49204a6 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 26 Oct 2021 18:29:30 +0200 Subject: [PATCH 128/128] Made the menu texture render correctly when running really low on texture memory. --- es-core/src/components/NinePatchComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/components/NinePatchComponent.cpp b/es-core/src/components/NinePatchComponent.cpp index ad6eaab3f..e1b62b366 100644 --- a/es-core/src/components/NinePatchComponent.cpp +++ b/es-core/src/components/NinePatchComponent.cpp @@ -59,7 +59,7 @@ void NinePatchComponent::buildVertices() else scaleFactor = glm::clamp(Renderer::getScreenWidthModifier(), 0.4f, 3.0f); - mTexture = TextureResource::get(mPath, false, false, true, true, true, scaleFactor); + mTexture = TextureResource::get(mPath, false, false, false, true, true, scaleFactor); if (mTexture->getSize() == glm::ivec2{}) { mVertices = nullptr;