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