diff --git a/THEMES-DEV.md b/THEMES-DEV.md index e876c91e4..79af9725c 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -53,8 +53,9 @@ Again, the `[CURRENT_THEME_SET]` value is set in the "UI Settings" menu. If it Here is a very simple theme that changes the description text's color: ```xml + - 6 + 7 00FF00 @@ -73,11 +74,10 @@ Here is a very simple theme that changes the description text's color: Everything must be inside a `` tag. -**The `` tag *must* be specified**. This is the version of the theming system the theme was designed for. The current version is 6. +**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 EmulationStation. Views are defined like this: ```xml @@ -124,8 +124,9 @@ You can include theme files within theme files, similar to `#include` in C (thou `~/.emulationstation/all_themes.xml`: ```xml + - 6 + 7 ./all_themes/myfont.ttf @@ -137,8 +138,9 @@ You can include theme files within theme files, similar to `#include` in C (thou `~/.emulationstation/snes/theme.xml`: ```xml + - 6 + 7 ./../all_themes.xml @@ -150,8 +152,9 @@ You can include theme files within theme files, similar to `#include` in C (thou Is equivalent to this `snes/theme.xml`: ```xml + - 6 + 7 ./all_themes/myfont.ttf @@ -169,8 +172,9 @@ Notice that properties that were not specified got merged (``) and the 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: ```xml + - 6 + 7 ./snes_art/snes_header.png @@ -186,8 +190,9 @@ Sometimes you want to apply the same properties to the same elements across mult This is equivalent to: ```xml + - 6 + 7 ./snes_art/snes_header.png @@ -218,8 +223,9 @@ This is equivalent to: 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: ```xml + - 6 + 7 48474D @@ -290,8 +297,9 @@ 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 + - 6 + 7 @@ -851,13 +859,53 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice #### helpsystem -* `pos` - type: NORMALIZED_PAIR. Default is "0.012 0.9515" +* `pos` - type: NORMALIZED_PAIR. Default is "0.012 0.9515" * `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. -* `textColor` - type: COLOR. Default is 777777FF. -* `iconColor` - type: COLOR. Default is 777777FF. + - 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. +* `textColor` - type: COLOR. Default is 777777FF. +* `textColorDimmed` - type: COLOR. Default is 777777FF. +* `iconColor` - type: COLOR. Default is 777777FF. +* `iconColorDimmed` - type: COLOR. Default is 777777FF. * `fontPath` - type: PATH. * `fontSize` - type: FLOAT. +* `entrySpacing` - type: FLOAT. Default is 16.0. + - Spacing in pixels between the help system components. +* `iconTextSpacing` - type: FLOAT. Default is 8.0. + - Spacing in pixels within a help system component between it's icon and text. +* `textStyle` - type: STRING. Default is `uppercase`. + - The style of the text. Options: `uppercase`, `lowercase`, `camelcase`. +* `customButtonIcon` - type: PATH. + - A button icon override. Specify the button type in the attribute `button`. The available buttons are: + `dpad_updown`, + `dpad_leftright`, + `dpad_all`, + `thumbstick_click`, + `button_l`, + `button_r`, + `button_lr`, + `button_a_SNES`, + `button_b_SNES`, + `button_x_SNES`, + `button_y_SNES`, + `button_back_SNES`, + `button_start_SNES`, + `button_a_PS`, + `button_b_PS`, + `button_x_PS`, + `button_y_PS`, + `button_back_PS4`, + `button_start_PS4`, + `button_back_PS5`, + `button_start_PS5`, + `button_a_XBOX`, + `button_b_XBOX`, + `button_x_XBOX`, + `button_y_XBOX`, + `button_back_XBOX`, + `button_start_XBOX`, + `button_back_XBOX360`, + `button_start_XBOX360`. #### carousel diff --git a/THEMES.md b/THEMES.md index 7c2b127a3..32d86757b 100644 --- a/THEMES.md +++ b/THEMES.md @@ -71,20 +71,18 @@ Here is a very simple theme that changes the description text's color: Everything must be inside a `` tag. -**The `` tag *must* be specified**. This is the version of the theming system the theme was designed for. The current version is 6. +**The `` tag *must* be specified**. This is the version of the theming system the theme was designed for. +The current version is 6. - - -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 EmulationStation. Views are defined like this: ```xml + ... define elements here ... ``` - - 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: ```xml @@ -122,6 +120,7 @@ You can include theme files within theme files, similar to `#include` in C (thou `~/.emulationstation/all_themes.xml`: ```xml + 6 @@ -135,6 +134,7 @@ You can include theme files within theme files, similar to `#include` in C (thou `~/.emulationstation/snes/theme.xml`: ```xml + 6 ./../all_themes.xml @@ -148,6 +148,7 @@ You can include theme files within theme files, similar to `#include` in C (thou Is equivalent to this `snes/theme.xml`: ```xml + 6 @@ -167,6 +168,7 @@ Notice that properties that were not specified got merged (``) and the 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: ```xml + 6 @@ -184,6 +186,7 @@ Sometimes you want to apply the same properties to the same elements across mult This is equivalent to: ```xml + 6 @@ -216,6 +219,7 @@ This is equivalent to: 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: ```xml + 6 @@ -230,6 +234,7 @@ You can theme multiple elements *of the same type* simultaneously. The `name` a Which is equivalent to: ```xml + 6 @@ -288,6 +293,7 @@ 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 + 6 @@ -849,11 +855,12 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice #### helpsystem -* `pos` - type: NORMALIZED_PAIR. Default is "0.012 0.9515" +* `pos` - type: NORMALIZED_PAIR. Default is "0.012 0.9515" * `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. -* `textColor` - type: COLOR. Default is 777777FF. -* `iconColor` - type: COLOR. Default is 777777FF. + - 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. +* `textColor` - type: COLOR. Default is 777777FF. +* `iconColor` - type: COLOR. Default is 777777FF. * `fontPath` - type: PATH. * `fontSize` - type: FLOAT. diff --git a/es-core/src/HelpStyle.cpp b/es-core/src/HelpStyle.cpp index 2a6cb917a..b351906f7 100644 --- a/es-core/src/HelpStyle.cpp +++ b/es-core/src/HelpStyle.cpp @@ -16,8 +16,13 @@ HelpStyle::HelpStyle() position = glm::vec2{Renderer::getScreenWidth() * 0.012f, Renderer::getScreenHeight() * 0.9515f}; origin = glm::vec2{}; - iconColor = 0x777777FF; textColor = 0x777777FF; + textColorDimmed = 0x777777FF; + iconColor = 0x777777FF; + iconColorDimmed = 0x777777FF; + entrySpacing = 16.0f; + iconTextSpacing = 8.0f; + textStyle = "uppercase"; if (FONT_SIZE_SMALL != 0) font = Font::get(FONT_SIZE_SMALL); @@ -42,9 +47,92 @@ void HelpStyle::applyTheme(const std::shared_ptr& theme, const std::s if (elem->has("textColor")) textColor = elem->get("textColor"); + if (elem->has("textColorDimmed")) + textColorDimmed = elem->get("textColorDimmed"); + if (elem->has("iconColor")) iconColor = elem->get("iconColor"); + if (elem->has("iconColorDimmed")) + iconColorDimmed = elem->get("iconColorDimmed"); + if (elem->has("fontPath") || elem->has("fontSize")) font = Font::getFromTheme(elem, ThemeFlags::ALL, font); + + if (elem->has("entrySpacing")) + entrySpacing = elem->get("entrySpacing"); + + if (elem->has("iconTextSpacing")) + iconTextSpacing = elem->get("iconTextSpacing"); + + if (elem->has("textStyle")) + textStyle = elem->get("textStyle"); + + // Load custom button icons. + + // General. + if (elem->has("dpad_updown")) + mCustomButtons.dpad_updown = elem->get("dpad_updown"); + if (elem->has("dpad_leftright")) + mCustomButtons.dpad_leftright = elem->get("dpad_leftright"); + if (elem->has("dpad_all")) + mCustomButtons.dpad_all = elem->get("dpad_all"); + if (elem->has("thumbstick_click")) + mCustomButtons.thumbstick_click = elem->get("thumbstick_click"); + if (elem->has("button_l")) + mCustomButtons.button_l = elem->get("button_l"); + if (elem->has("button_r")) + mCustomButtons.button_r = elem->get("button_r"); + if (elem->has("button_lr")) + mCustomButtons.button_lr = elem->get("button_lr"); + + // SNES. + if (elem->has("button_a_SNES")) + mCustomButtons.button_a_SNES = elem->get("button_a_SNES"); + if (elem->has("button_b_SNES")) + mCustomButtons.button_b_SNES = elem->get("button_b_SNES"); + if (elem->has("button_x_SNES")) + mCustomButtons.button_x_SNES = elem->get("button_x_SNES"); + if (elem->has("button_y_SNES")) + mCustomButtons.button_y_SNES = elem->get("button_y_SNES"); + if (elem->has("button_start_SNES")) + mCustomButtons.button_start_SNES = elem->get("button_start_SNES"); + if (elem->has("button_back_SNES")) + mCustomButtons.button_back_SNES = elem->get("button_back_SNES"); + + // PS. + if (elem->has("button_a_PS")) + mCustomButtons.button_a_PS = elem->get("button_a_PS"); + if (elem->has("button_b_PS")) + mCustomButtons.button_b_PS = elem->get("button_b_PS"); + if (elem->has("button_x_PS")) + mCustomButtons.button_x_PS = elem->get("button_x_PS"); + if (elem->has("button_y_PS")) + mCustomButtons.button_y_PS = elem->get("button_y_PS"); + if (elem->has("button_start_PS4")) + mCustomButtons.button_start_PS4 = elem->get("button_start_PS4"); + if (elem->has("button_back_PS4")) + mCustomButtons.button_back_PS4 = elem->get("button_back_PS4"); + if (elem->has("button_start_PS5")) + mCustomButtons.button_start_PS5 = elem->get("button_start_PS5"); + if (elem->has("button_back_PS5")) + mCustomButtons.button_back_PS5 = elem->get("button_back_PS5"); + + // XBOX. + if (elem->has("button_a_XBOX")) + mCustomButtons.button_a_XBOX = elem->get("button_a_XBOX"); + if (elem->has("button_b_XBOX")) + mCustomButtons.button_b_XBOX = elem->get("button_b_XBOX"); + if (elem->has("button_x_XBOX")) + mCustomButtons.button_x_XBOX = elem->get("button_x_XBOX"); + if (elem->has("button_y_XBOX")) + mCustomButtons.button_y_XBOX = elem->get("button_y_XBOX"); + if (elem->has("button_start_XBOX")) + mCustomButtons.button_start_XBOX = elem->get("button_start_XBOX"); + if (elem->has("button_back_XBOX")) + mCustomButtons.button_back_XBOX = elem->get("button_back_XBOX"); + if (elem->has("button_start_XBOX360")) + mCustomButtons.button_start_XBOX360 = elem->get("button_start_XBOX360"); + if (elem->has("button_back_XBOX360")) + mCustomButtons.button_back_XBOX360 = elem->get("button_back_XBOX360"); } diff --git a/es-core/src/HelpStyle.h b/es-core/src/HelpStyle.h index b6eb62e68..737123ad0 100644 --- a/es-core/src/HelpStyle.h +++ b/es-core/src/HelpStyle.h @@ -21,9 +21,56 @@ class ThemeData; struct HelpStyle { glm::vec2 position; glm::vec2 origin; - unsigned int iconColor; unsigned int textColor; + unsigned int textColorDimmed; + unsigned int iconColor; + unsigned int iconColorDimmed; std::shared_ptr font; + float entrySpacing; + float iconTextSpacing; + std::string textStyle; + + struct CustomButtonIcons { + + // General. + std::string dpad_updown; + std::string dpad_leftright; + std::string dpad_all; + std::string thumbstick_click; + std::string button_l; + std::string button_r; + std::string button_lr; + + // SNES. + std::string button_a_SNES; + std::string button_b_SNES; + std::string button_x_SNES; + std::string button_y_SNES; + std::string button_start_SNES; + std::string button_back_SNES; + + // PS. + std::string button_a_PS; + std::string button_b_PS; + std::string button_x_PS; + std::string button_y_PS; + std::string button_start_PS4; + std::string button_back_PS4; + std::string button_start_PS5; + std::string button_back_PS5; + + // XBOX. + std::string button_a_XBOX; + std::string button_b_XBOX; + std::string button_x_XBOX; + std::string button_y_XBOX; + std::string button_start_XBOX; + std::string button_back_XBOX; + std::string button_start_XBOX360; + std::string button_back_XBOX360; + }; + + CustomButtonIcons mCustomButtons; HelpStyle(); // Default values. void applyTheme(const std::shared_ptr& theme, const std::string& view); diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 2af478067..5de869eb5 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -151,9 +151,15 @@ std::map> The {{"pos", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, {"textColor", COLOR}, + {"textColorDimmed", COLOR}, {"iconColor", COLOR}, + {"iconColorDimmed", COLOR}, {"fontPath", PATH}, - {"fontSize", FLOAT}}}, + {"fontSize", FLOAT}, + {"entrySpacing", FLOAT}, + {"iconTextSpacing", FLOAT}, + {"textStyle", STRING}, + {"customButtonIcon", PATH}}}, {"navigationsounds", {{"systembrowseSound", PATH}, {"quicksysselectSound", PATH}, @@ -192,7 +198,7 @@ std::map> The {"zIndex", FLOAT}}}}; #define MINIMUM_THEME_FORMAT_VERSION 3 -#define CURRENT_THEME_FORMAT_VERSION 6 +#define CURRENT_THEME_FORMAT_VERSION 7 // Helper. unsigned int getHexColor(const std::string& str) @@ -496,7 +502,20 @@ void ThemeData::parseElement(const pugi::xml_node& root, << ((node.text().get() != path) ? "which resolves to \"" + path + "\"" : ""); } - element.properties[node.name()] = path; + + // Special parsing instruction for customButtonIcon -> save node as it's button + // 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) + LOG(LogError) + << " element requires the `button` property."; + else + element.properties[btn] = path; + } + else + element.properties[node.name()] = path; + break; } case COLOR: { diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 6dab2c03e..8b021ed9b 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -359,6 +359,11 @@ void Window::update(int deltaTime) mScreensaver->update(deltaTime); } +bool Window::isBackgroundDimmed() +{ + return !mGuiStack.empty() && (mGuiStack.front() != mGuiStack.back() || mRenderLaunchScreen); +} + void Window::render() { glm::mat4 trans{Renderer::getIdentity()}; @@ -366,7 +371,7 @@ void Window::render() mRenderedHelpPrompts = false; // Draw only bottom and top of GuiStack (if they are different). - if (mGuiStack.size()) { + if (!mGuiStack.empty()) { auto& bottom = mGuiStack.front(); auto& top = mGuiStack.back(); @@ -408,7 +413,7 @@ void Window::render() unsigned char* processedTexture = new unsigned char[Renderer::getScreenWidth() * Renderer::getScreenHeight() * 4]; - // Defocus the background using multiple passes of gaussian blur, with the number + // De-focus the background using multiple passes of gaussian blur, with the number // of iterations relative to the screen resolution. Renderer::shaderParameters backgroundParameters; diff --git a/es-core/src/Window.h b/es-core/src/Window.h index 2f68f7cc2..193053c43 100644 --- a/es-core/src/Window.h +++ b/es-core/src/Window.h @@ -88,6 +88,7 @@ public: void removeGui(GuiComponent* gui); GuiComponent* peekGui(); int getGuiStackSize() { return static_cast(mGuiStack.size()); } + bool isBackgroundDimmed(); bool init(); void deinit(); diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index d04218bab..4a6bfb28b 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -10,15 +10,13 @@ #include "Log.h" #include "Settings.h" +#include "Window.h" #include "components/ComponentGrid.h" #include "components/ImageComponent.h" #include "components/TextComponent.h" #include "resources/TextureResource.h" #include "utils/StringUtil.h" -#define ICON_TEXT_SPACING 8.0f // Space between [icon] and [text] (px). -#define ENTRY_SPACING 16.0f // Space between [text] and next [icon] (px). - static std::map sIconPathMap{}; HelpComponent::HelpComponent(Window* window) @@ -31,58 +29,144 @@ void HelpComponent::assignIcons() { std::string controllerType = Settings::getInstance()->getString("InputControllerType"); + std::map sIconPathMapOld(sIconPathMap); sIconPathMap.clear(); // These graphics files are common between all controller types. - sIconPathMap["up/down"] = ":/help/dpad_updown.svg"; - sIconPathMap["left/right"] = ":/help/dpad_leftright.svg"; - sIconPathMap["up/down/left/right"] = ":/help/dpad_all.svg"; - sIconPathMap["thumbstickclick"] = ":/help/thumbstick_click.svg"; - sIconPathMap["l"] = ":/help/button_l.svg"; - sIconPathMap["r"] = ":/help/button_r.svg"; - sIconPathMap["lr"] = ":/help/button_lr.svg"; + sIconPathMap["up/down"] = mStyle.mCustomButtons.dpad_updown.empty() ? + ":/help/dpad_updown.svg" : + mStyle.mCustomButtons.dpad_updown; + sIconPathMap["left/right"] = mStyle.mCustomButtons.dpad_leftright.empty() ? + ":/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; + sIconPathMap["thumbstickclick"] = mStyle.mCustomButtons.thumbstick_click.empty() ? + ":/help/thumbstick_click.svg" : + mStyle.mCustomButtons.thumbstick_click; + sIconPathMap["l"] = mStyle.mCustomButtons.button_l.empty() ? ":/help/button_l.svg" : + mStyle.mCustomButtons.button_l; + sIconPathMap["r"] = mStyle.mCustomButtons.button_r.empty() ? ":/help/button_r.svg" : + mStyle.mCustomButtons.button_r; + sIconPathMap["lr"] = mStyle.mCustomButtons.button_lr.empty() ? ":/help/button_lr.svg" : + mStyle.mCustomButtons.button_lr; // These graphics files are custom per controller type. if (controllerType == "snes") { - sIconPathMap["a"] = ":/help/button_a_SNES.svg"; - sIconPathMap["b"] = ":/help/button_b_SNES.svg"; - sIconPathMap["x"] = ":/help/button_x_SNES.svg"; - sIconPathMap["y"] = ":/help/button_y_SNES.svg"; - sIconPathMap["start"] = ":/help/button_start_SNES.svg"; - sIconPathMap["back"] = ":/help/button_back_SNES.svg"; + sIconPathMap["a"] = mStyle.mCustomButtons.button_a_SNES.empty() ? + ":/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; + sIconPathMap["x"] = mStyle.mCustomButtons.button_x_SNES.empty() ? + ":/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; + sIconPathMap["start"] = mStyle.mCustomButtons.button_start_SNES.empty() ? + ":/help/button_start_SNES.svg" : + mStyle.mCustomButtons.button_start_SNES; + sIconPathMap["back"] = mStyle.mCustomButtons.button_back_SNES.empty() ? + ":/help/button_back_SNES.svg" : + mStyle.mCustomButtons.button_back_SNES; } else if (controllerType == "ps4") { - sIconPathMap["a"] = ":/help/button_a_PS.svg"; - sIconPathMap["b"] = ":/help/button_b_PS.svg"; - sIconPathMap["x"] = ":/help/button_x_PS.svg"; - sIconPathMap["y"] = ":/help/button_y_PS.svg"; - sIconPathMap["start"] = ":/help/button_start_PS4.svg"; - sIconPathMap["back"] = ":/help/button_back_PS4.svg"; + sIconPathMap["a"] = mStyle.mCustomButtons.button_a_PS.empty() ? + ":/help/button_a_PS.svg" : + mStyle.mCustomButtons.button_a_PS; + sIconPathMap["b"] = mStyle.mCustomButtons.button_b_PS.empty() ? + ":/help/button_b_PS.svg" : + mStyle.mCustomButtons.button_b_PS; + sIconPathMap["x"] = mStyle.mCustomButtons.button_x_PS.empty() ? + ":/help/button_x_PS.svg" : + mStyle.mCustomButtons.button_x_PS; + sIconPathMap["y"] = mStyle.mCustomButtons.button_y_PS.empty() ? + ":/help/button_y_PS.svg" : + mStyle.mCustomButtons.button_y_PS; + sIconPathMap["start"] = mStyle.mCustomButtons.button_start_PS4.empty() ? + ":/help/button_start_PS4.svg" : + mStyle.mCustomButtons.button_start_PS4; + sIconPathMap["back"] = mStyle.mCustomButtons.button_back_PS4.empty() ? + ":/help/button_back_PS4.svg" : + mStyle.mCustomButtons.button_back_PS4; } else if (controllerType == "ps5") { - sIconPathMap["a"] = ":/help/button_a_PS.svg"; - sIconPathMap["b"] = ":/help/button_b_PS.svg"; - sIconPathMap["x"] = ":/help/button_x_PS.svg"; - sIconPathMap["y"] = ":/help/button_y_PS.svg"; - sIconPathMap["start"] = ":/help/button_start_PS5.svg"; - sIconPathMap["back"] = ":/help/button_back_PS5.svg"; + sIconPathMap["a"] = mStyle.mCustomButtons.button_a_PS.empty() ? + ":/help/button_a_PS.svg" : + mStyle.mCustomButtons.button_a_PS; + sIconPathMap["b"] = mStyle.mCustomButtons.button_b_PS.empty() ? + ":/help/button_b_PS.svg" : + mStyle.mCustomButtons.button_b_PS; + sIconPathMap["x"] = mStyle.mCustomButtons.button_x_PS.empty() ? + ":/help/button_x_PS.svg" : + mStyle.mCustomButtons.button_x_PS; + sIconPathMap["y"] = mStyle.mCustomButtons.button_y_PS.empty() ? + ":/help/button_y_PS.svg" : + mStyle.mCustomButtons.button_y_PS; + sIconPathMap["start"] = mStyle.mCustomButtons.button_start_PS5.empty() ? + ":/help/button_start_PS5.svg" : + mStyle.mCustomButtons.button_start_PS5; + sIconPathMap["back"] = mStyle.mCustomButtons.button_back_PS5.empty() ? + ":/help/button_back_PS5.svg" : + mStyle.mCustomButtons.button_back_PS5; } else if (controllerType == "xbox360") { - sIconPathMap["a"] = ":/help/button_a_XBOX.svg"; - sIconPathMap["b"] = ":/help/button_b_XBOX.svg"; - sIconPathMap["x"] = ":/help/button_x_XBOX.svg"; - sIconPathMap["y"] = ":/help/button_y_XBOX.svg"; - sIconPathMap["start"] = ":/help/button_start_XBOX360.svg"; - sIconPathMap["back"] = ":/help/button_back_XBOX360.svg"; + + sIconPathMap["a"] = mStyle.mCustomButtons.button_a_XBOX.empty() ? + ":/help/button_a_XBOX.svg" : + mStyle.mCustomButtons.button_a_XBOX; + sIconPathMap["b"] = mStyle.mCustomButtons.button_b_XBOX.empty() ? + ":/help/button_b_XBOX.svg" : + mStyle.mCustomButtons.button_b_XBOX; + sIconPathMap["x"] = mStyle.mCustomButtons.button_x_XBOX.empty() ? + ":/help/button_x_XBOX.svg" : + mStyle.mCustomButtons.button_x_XBOX; + sIconPathMap["y"] = mStyle.mCustomButtons.button_y_XBOX.empty() ? + ":/help/button_y_XBOX.svg" : + mStyle.mCustomButtons.button_y_XBOX; + sIconPathMap["start"] = mStyle.mCustomButtons.button_start_XBOX360.empty() ? + ":/help/button_start_XBOX360.svg" : + mStyle.mCustomButtons.button_start_XBOX360; + sIconPathMap["back"] = mStyle.mCustomButtons.button_back_XBOX360.empty() ? + ":/help/button_back_XBOX360.svg" : + mStyle.mCustomButtons.button_back_XBOX360; } else { // Xbox One and later. - sIconPathMap["a"] = ":/help/button_a_XBOX.svg"; - sIconPathMap["b"] = ":/help/button_b_XBOX.svg"; - sIconPathMap["x"] = ":/help/button_x_XBOX.svg"; - sIconPathMap["y"] = ":/help/button_y_XBOX.svg"; - sIconPathMap["start"] = ":/help/button_start_XBOX.svg"; - sIconPathMap["back"] = ":/help/button_back_XBOX.svg"; + sIconPathMap["a"] = mStyle.mCustomButtons.button_a_XBOX.empty() ? + ":/help/button_a_XBOX.svg" : + mStyle.mCustomButtons.button_a_XBOX; + sIconPathMap["b"] = mStyle.mCustomButtons.button_b_XBOX.empty() ? + ":/help/button_b_XBOX.svg" : + mStyle.mCustomButtons.button_b_XBOX; + sIconPathMap["x"] = mStyle.mCustomButtons.button_x_XBOX.empty() ? + ":/help/button_x_XBOX.svg" : + mStyle.mCustomButtons.button_x_XBOX; + sIconPathMap["y"] = mStyle.mCustomButtons.button_y_XBOX.empty() ? + ":/help/button_y_XBOX.svg" : + mStyle.mCustomButtons.button_y_XBOX; + sIconPathMap["start"] = mStyle.mCustomButtons.button_start_XBOX.empty() ? + ":/help/button_start_XBOX.svg" : + mStyle.mCustomButtons.button_start_XBOX; + sIconPathMap["back"] = mStyle.mCustomButtons.button_back_XBOX.empty() ? + ":/help/button_back_XBOX.svg" : + mStyle.mCustomButtons.button_back_XBOX; + } + + // Invalidate cache for icons that have changed. + auto it = sIconPathMap.begin(); + while (it != sIconPathMap.end()) { + if (sIconPathMapOld.find(it->first) != sIconPathMapOld.end()) { + if (sIconPathMapOld[it->first] != sIconPathMap[it->first]) { + if (mIconCache.find(it->first) != mIconCache.end()) { + mIconCache.erase(mIconCache.find(it->first)); + } + } + } + it++; } } @@ -102,6 +186,7 @@ void HelpComponent::setStyle(const HelpStyle& style) { mStyle = style; updateGrid(); + assignIcons(); } void HelpComponent::updateGrid() @@ -124,19 +209,31 @@ void HelpComponent::updateGrid() float width = 0; const float height = std::round(font->getLetterHeight() * 1.25f); + // State variable indicating whether gui is dimmed. + bool isDimmed = mWindow->isBackgroundDimmed(); + for (auto it = mPrompts.cbegin(); it != mPrompts.cend(); it++) { auto icon = std::make_shared(mWindow); icon->setImage(getIconTexture(it->first.c_str())); - icon->setColorShift(mStyle.iconColor); + icon->setColorShift(isDimmed ? mStyle.iconColorDimmed : mStyle.iconColor); icon->setResize(0, height); icons.push_back(icon); - auto lbl = std::make_shared(mWindow, Utils::String::toUpper(it->second), - font, mStyle.textColor); + // Apply text style and color from the theme to the label and add it to the label list. + std::string lblInput = it->second; + if (mStyle.textStyle == "lowercase") + lblInput = Utils::String::toLower(lblInput); + else if (mStyle.textStyle == "camelcase") + lblInput = Utils::String::toCamelCase(lblInput); + else + lblInput = Utils::String::toUpper(lblInput); + auto lbl = std::make_shared( + mWindow, lblInput, font, isDimmed ? mStyle.textColorDimmed : mStyle.textColor); labels.push_back(lbl); - width += icon->getSize().x + lbl->getSize().x + - ((ICON_TEXT_SPACING + ENTRY_SPACING) * Renderer::getScreenWidthModifier()); + width += + icon->getSize().x + lbl->getSize().x + + ((mStyle.iconTextSpacing + mStyle.entrySpacing) * Renderer::getScreenWidthModifier()); } mGrid->setSize(width, height); @@ -144,8 +241,8 @@ void HelpComponent::updateGrid() for (unsigned int i = 0; i < icons.size(); i++) { const int col = i * 4; mGrid->setColWidthPerc(col, icons.at(i)->getSize().x / width); - mGrid->setColWidthPerc(col + 1, - (ICON_TEXT_SPACING * Renderer::getScreenWidthModifier()) / width); + mGrid->setColWidthPerc( + col + 1, (mStyle.iconTextSpacing * Renderer::getScreenWidthModifier()) / width); mGrid->setColWidthPerc(col + 2, labels.at(i)->getSize().x / width); mGrid->setEntry(icons.at(i), glm::ivec2{col, 0}, false, false); @@ -167,6 +264,7 @@ std::shared_ptr HelpComponent::getIconTexture(const char* name) LOG(LogError) << "Unknown help icon \"" << name << "\""; return nullptr; } + if (!ResourceManager::getInstance()->fileExists(pathLookup->second)) { LOG(LogError) << "Couldn't load help icon \"" << name << "\" as the file \"" << pathLookup->second << "\" is missing"; diff --git a/es-core/src/utils/StringUtil.cpp b/es-core/src/utils/StringUtil.cpp index edb2f56d2..2bc8798ac 100644 --- a/es-core/src/utils/StringUtil.cpp +++ b/es-core/src/utils/StringUtil.cpp @@ -541,6 +541,27 @@ namespace Utils return stringUpper; } + std::string toCamelCase(const std::string& stringArg) + { + std::string line = stringArg; + bool active = true; + + for (int i = 0; line[i] != '\0'; i++) { + if (std::isalpha(line[i])) { + if (active) { + line[i] = Utils::String::toUpper(std::string(1, line[i]))[0]; + active = false; + } + else + line[i] = Utils::String::toLower(std::string(1, line[i]))[0]; + } + else if (line[i] == ' ') + active = true; + } + + return line; + } + std::string trim(const std::string& stringArg) { const size_t strBegin = stringArg.find_first_not_of(" \t"); diff --git a/es-core/src/utils/StringUtil.h b/es-core/src/utils/StringUtil.h index 98b1ecb23..28e9c9060 100644 --- a/es-core/src/utils/StringUtil.h +++ b/es-core/src/utils/StringUtil.h @@ -26,6 +26,7 @@ namespace Utils size_t moveCursor(const std::string& stringArg, const size_t cursor, const int amount); std::string toLower(const std::string& stringArg); std::string toUpper(const std::string& stringArg); + std::string toCamelCase(const std::string& stringArg); std::string trim(const std::string& stringArg); std::string replace(const std::string& stringArg, const std::string& replace,