From 8b5b68585c0a9d0e27683fd7f1465f848a8971fe Mon Sep 17 00:00:00 2001 From: QwertyChouskie Date: Sat, 30 Oct 2021 18:55:56 -0700 Subject: [PATCH] Allow specifying a base skin Anything not provided or defined by a skin will be used from the defined base skin, if specified. For example, if a skin provides a base_theme of "cartoon", any icons/buttons/etc. not provided by the skin will be pulled from "cartoon". --- data/skins/cartoon/stkskin.xml | 25 +++-- data/skins/coal/stkskin.xml | 24 ++++- data/skins/forest/stkskin.xml | 24 ++++- data/skins/ocean/stkskin.xml | 24 ++++- data/skins/peach/stkskin.xml | 24 ++--- data/skins/ruby/stkskin.xml | 24 ++++- src/guiengine/skin.cpp | 166 ++++++++++++++++++++++----------- 7 files changed, 226 insertions(+), 85 deletions(-) diff --git a/data/skins/cartoon/stkskin.xml b/data/skins/cartoon/stkskin.xml index bba389570..921076a23 100644 --- a/data/skins/cartoon/stkskin.xml +++ b/data/skins/cartoon/stkskin.xml @@ -61,30 +61,29 @@ and explicitely specify which parts you want to see. The 4 corner areas are only when the border that intersect at this corner are enabled. When there is a common="y" with image tag, the image will be loaded only from data/skins/common in stk-code. + +Any information not specified in this theme will be inherited from the specified base theme, +if any. To specify a base theme, add base_theme="themename" to the tag. + +To use an icon theme, place the replacement icons (PNG or SVG) into [skin folder]/data/gui/icons +STK will prefer these icons first, if not found it will fallback to icons from the base theme(s). --> - + diff --git a/data/skins/coal/stkskin.xml b/data/skins/coal/stkskin.xml index 06e096f88..2eb135af8 100644 --- a/data/skins/coal/stkskin.xml +++ b/data/skins/coal/stkskin.xml @@ -58,14 +58,34 @@ that take a float from 0 to 1, representing the percentage of each border that g area (this might include stuff like shadows, etc.). The 'h' one is for horizontal borders, the 'v' one is for vertical borders. -Finnally : the image is split, as shown above, into 9 areas. In osme cases, you may not want +Finnally : the image is split, as shown above, into 9 areas. In some cases, you may not want all areas to be rendered. Then you can pass parameter areas="body+left+right+top+bottom" and explicitely specify which parts you want to see. The 4 corner areas are only visible when the border that intersect at this corner are enabled. +When there is a common="y" with image tag, the image will be loaded only from data/skins/common in stk-code. + +Any information not specified in this theme will be inherited from the specified base theme, +if any. To specify a base theme, add base_theme="themename" to the tag. + +To use an icon theme, place the replacement icons (PNG or SVG) into [skin folder]/data/gui/icons +STK will prefer these icons first, if not found it will fallback to icons from the base theme(s). --> - + + + diff --git a/data/skins/forest/stkskin.xml b/data/skins/forest/stkskin.xml index ad072845b..161df29a3 100644 --- a/data/skins/forest/stkskin.xml +++ b/data/skins/forest/stkskin.xml @@ -58,14 +58,34 @@ that take a float from 0 to 1, representing the percentage of each border that g area (this might include stuff like shadows, etc.). The 'h' one is for horizontal borders, the 'v' one is for vertical borders. -Finnally : the image is split, as shown above, into 9 areas. In osme cases, you may not want +Finnally : the image is split, as shown above, into 9 areas. In some cases, you may not want all areas to be rendered. Then you can pass parameter areas="body+left+right+top+bottom" and explicitely specify which parts you want to see. The 4 corner areas are only visible when the border that intersect at this corner are enabled. +When there is a common="y" with image tag, the image will be loaded only from data/skins/common in stk-code. + +Any information not specified in this theme will be inherited from the specified base theme, +if any. To specify a base theme, add base_theme="themename" to the tag. + +To use an icon theme, place the replacement icons (PNG or SVG) into [skin folder]/data/gui/icons +STK will prefer these icons first, if not found it will fallback to icons from the base theme(s). --> - + + + diff --git a/data/skins/ocean/stkskin.xml b/data/skins/ocean/stkskin.xml index 7dd5c55d4..c9e0ee366 100644 --- a/data/skins/ocean/stkskin.xml +++ b/data/skins/ocean/stkskin.xml @@ -57,14 +57,34 @@ that take a float from 0 to 1, representing the percentage of each border that g area (this might include stuff like shadows, etc.). The 'h' one is for horizontal borders, the 'v' one is for vertical borders. -Finnally : the image is split, as shown above, into 9 areas. In osme cases, you may not want +Finnally : the image is split, as shown above, into 9 areas. In some cases, you may not want all areas to be rendered. Then you can pass parameter areas="body+left+right+top+bottom" and explicitely specify which parts you want to see. The 4 corner areas are only visible when the border that intersect at this corner are enabled. +When there is a common="y" with image tag, the image will be loaded only from data/skins/common in stk-code. + +Any information not specified in this theme will be inherited from the specified base theme, +if any. To specify a base theme, add base_theme="themename" to the tag. + +To use an icon theme, place the replacement icons (PNG or SVG) into [skin folder]/data/gui/icons +STK will prefer these icons first, if not found it will fallback to icons from the base theme(s). --> - + + + diff --git a/data/skins/peach/stkskin.xml b/data/skins/peach/stkskin.xml index 9e6f4d062..4b63c08dd 100644 --- a/data/skins/peach/stkskin.xml +++ b/data/skins/peach/stkskin.xml @@ -57,31 +57,31 @@ that take a float from 0 to 1, representing the percentage of each border that g area (this might include stuff like shadows, etc.). The 'h' one is for horizontal borders, the 'v' one is for vertical borders. -Finnally : the image is split, as shown above, into 9 areas. In osme cases, you may not want +Finnally : the image is split, as shown above, into 9 areas. In some cases, you may not want all areas to be rendered. Then you can pass parameter areas="body+left+right+top+bottom" and explicitely specify which parts you want to see. The 4 corner areas are only visible when the border that intersect at this corner are enabled. When there is a common="y" with image tag, the image will be loaded only from data/skins/common in stk-code. + +Any information not specified in this theme will be inherited from the specified base theme, +if any. To specify a base theme, add base_theme="themename" to the tag. + +To use an icon theme, place the replacement icons (PNG or SVG) into [skin folder]/data/gui/icons +STK will prefer these icons first, if not found it will fallback to icons from the base theme(s). --> diff --git a/data/skins/ruby/stkskin.xml b/data/skins/ruby/stkskin.xml index cb950c741..6a265262e 100644 --- a/data/skins/ruby/stkskin.xml +++ b/data/skins/ruby/stkskin.xml @@ -58,14 +58,34 @@ that take a float from 0 to 1, representing the percentage of each border that g area (this might include stuff like shadows, etc.). The 'h' one is for horizontal borders, the 'v' one is for vertical borders. -Finnally : the image is split, as shown above, into 9 areas. In osme cases, you may not want +Finnally : the image is split, as shown above, into 9 areas. In some cases, you may not want all areas to be rendered. Then you can pass parameter areas="body+left+right+top+bottom" and explicitely specify which parts you want to see. The 4 corner areas are only visible when the border that intersect at this corner are enabled. +When there is a common="y" with image tag, the image will be loaded only from data/skins/common in stk-code. + +Any information not specified in this theme will be inherited from the specified base theme, +if any. To specify a base theme, add base_theme="themename" to the tag. + +To use an icon theme, place the replacement icons (PNG or SVG) into [skin folder]/data/gui/icons +STK will prefer these icons first, if not found it will fallback to icons from the base theme(s). --> - + + + diff --git a/src/guiengine/skin.cpp b/src/guiengine/skin.cpp index 92a0b7d0d..4bb6fd10b 100644 --- a/src/guiengine/skin.cpp +++ b/src/guiengine/skin.cpp @@ -57,7 +57,7 @@ namespace SkinConfig static std::vector m_normal_ttf; static std::vector m_digit_ttf; static std::string m_color_emoji_ttf; - static bool m_icon_theme; + static std::vector m_icon_theme_paths; static bool m_font; static void parseElement(const XMLNode* node) @@ -172,26 +172,29 @@ namespace SkinConfig * \brief loads skin information from a STK skin file * \throw std::runtime_error if file cannot be read */ - static void loadFromFile(std::string file) + static void loadFromFile(std::string file, bool clear_prev_params) { - // Clear global variables for android - m_render_params.clear(); - m_colors.clear(); m_data_path.clear(); - m_normal_ttf.clear(); - for (auto& p : stk_config->m_normal_ttf) - m_normal_ttf.push_back(file_manager->getAssetChecked(FileManager::TTF, p, true)); - m_digit_ttf.clear(); - for (auto& p : stk_config->m_digit_ttf) - m_digit_ttf.push_back(file_manager->getAssetChecked(FileManager::TTF, p, true)); - m_color_emoji_ttf.clear(); - if (!stk_config->m_color_emoji_ttf.empty()) + if (clear_prev_params) { - m_color_emoji_ttf = file_manager->getAssetChecked(FileManager::TTF, - stk_config->m_color_emoji_ttf, true); + // Clear global variables + m_render_params.clear(); + m_colors.clear(); + m_normal_ttf.clear(); + for (auto& p : stk_config->m_normal_ttf) + m_normal_ttf.push_back(file_manager->getAssetChecked(FileManager::TTF, p, true)); + m_digit_ttf.clear(); + for (auto& p : stk_config->m_digit_ttf) + m_digit_ttf.push_back(file_manager->getAssetChecked(FileManager::TTF, p, true)); + m_color_emoji_ttf.clear(); + if (!stk_config->m_color_emoji_ttf.empty()) + { + m_color_emoji_ttf = file_manager->getAssetChecked(FileManager::TTF, + stk_config->m_color_emoji_ttf, true); + } + m_icon_theme_paths.clear(); + m_font = false; } - m_icon_theme = false; - m_font = false; XMLNode* root = file_manager->createXMLTree(file); if(!root) @@ -203,6 +206,12 @@ namespace SkinConfig m_data_path = StringUtils::getPath(file_manager ->getFileSystem()->getAbsolutePath(file.c_str()).c_str()) + "/"; + + // Check for icon folder in theme, and add to search paths if present + if (file_manager->fileExists(m_data_path + "data/gui/icons/")) + m_icon_theme_paths.insert(m_icon_theme_paths.begin(), + m_data_path); + const int amount = root->getNumNodes(); for (int i=0; igetName() == "advanced") { - bool ret = false; - if (node->get("icon_theme", &ret)) - { - if (file_manager->fileExists(m_data_path + "data/gui/icons/")) - m_icon_theme = true; - } std::string color_ttf; if (node->get("color_emoji_ttf", &color_ttf)) { @@ -257,7 +260,6 @@ namespace SkinConfig list_ttf_path.clear(); if (node->get("digit_ttf", &list_ttf)) { - m_digit_ttf.clear(); for (auto& t : list_ttf) { std::string test_path = m_data_path + "data/ttf/" + t; @@ -281,6 +283,40 @@ namespace SkinConfig delete root; } // loadFromFile + std::vector getDependencyChain(std::string initial_skin_id) + { + std::vector chain; + chain.insert(chain.begin(), initial_skin_id); + + for(size_t i=0, n=chain.size(); igetAddonsFile( + std::string("skins/") + chain[0].substr(6) + "/stkskin.xml") : + file_manager->getAsset(FileManager::SKIN, chain[0] + "/stkskin.xml"); + + XMLNode* root = file_manager->createXMLTree(skin_file); + if (!root) + { + Log::error("skin", "Could not read XML file '%s'.", + skin_file.c_str()); + throw std::runtime_error("Invalid skin file"); + } + + std::string base_theme; + if (root->get("base_theme", &base_theme) != 0) + { + Log::info("GUI", "Inserting base theme %s into dependency chain", base_theme.c_str()); + chain.insert(chain.begin(), base_theme); + ++n; + } + + delete root; + } + + return chain; + } // getDependencyChain + // ------------------------------------------------------------------------ float getVerticalInnerPadding(int wtype, Widget* widget) { @@ -508,24 +544,45 @@ X##_yflip.LowerRightCorner.Y = y1;} Skin::Skin(IGUISkin* fallback_skin) { std::string skin_id = UserConfigParams::m_skin_file; - std::string skin_name = skin_id.find("addon_") != std::string::npos ? - file_manager->getAddonsFile( - std::string("skins/") + skin_id.substr(6) + "/stkskin.xml") : - file_manager->getAsset(FileManager::SKIN, skin_id + "/stkskin.xml"); try { - SkinConfig::loadFromFile(skin_name); + std::vector load_chain = SkinConfig::getDependencyChain(skin_id); + + bool reset = true; + for (auto skin_id : load_chain) + { + std::string skin_name = skin_id.find("addon_") != std::string::npos ? + file_manager->getAddonsFile( + std::string("skins/") + skin_id.substr(6) + "/stkskin.xml") : + file_manager->getAsset(FileManager::SKIN, skin_id + "/stkskin.xml"); + + Log::info("GUI", "Loading skin data from file: %s", skin_name.c_str()); + SkinConfig::loadFromFile(skin_name, reset); + reset = false; + } } catch (std::runtime_error& e) { (void)e; // avoid compiler warning // couldn't load skin. Try to revert to default + Log::error("GUI", "Could not load skin, reverting to default."); UserConfigParams::m_skin_file.revertToDefaults(); - std::string default_skin_id = UserConfigParams::m_skin_file; - skin_name = file_manager->getAsset(FileManager::SKIN, - default_skin_id + "/stkskin.xml"); - SkinConfig::loadFromFile(skin_name); + skin_id = UserConfigParams::m_skin_file; + std::vector load_chain = SkinConfig::getDependencyChain(skin_id); + + bool reset = true; + for (auto skin_id : load_chain) + { + std::string skin_name = skin_id.find("addon_") != std::string::npos ? + file_manager->getAddonsFile( + std::string("skins/") + skin_id.substr(6) + "/stkskin.xml") : + file_manager->getAsset(FileManager::SKIN, skin_id + "/stkskin.xml"); + + Log::info("GUI", "Loading skin data from file: %s", skin_name.c_str()); + SkinConfig::loadFromFile(skin_name, reset); + reset = false; + } } m_bg_image = NULL; @@ -2899,7 +2956,7 @@ const std::string& Skin::getColorEmojiTTF() const // ----------------------------------------------------------------------------- bool Skin::hasIconTheme() const { - return SkinConfig::m_icon_theme; + return SkinConfig::m_icon_theme_paths.size() > 0; } // hasIconTheme // ----------------------------------------------------------------------------- @@ -2913,31 +2970,36 @@ bool Skin::hasFont() const * icon. */ std::string Skin::getThemedIcon(const std::string& relative_path) const { - // First check if an svg icon is available + // File extensions to check const std::vector ext {".svg", ".png"}; - // get the path without extension + // Get the requested path without extension const std::string path_no_extension = StringUtils::removeExtension(relative_path); - // loop the file extensions: svg first - for(auto s : ext) + + // Look for the requested icon in each theme in the dependency chain + for (auto p : SkinConfig::m_icon_theme_paths) { - std::string relative_path2 = path_no_extension + s; - if (!SkinConfig::m_icon_theme || - (relative_path2.find("karts/") == std::string::npos && - relative_path2.find("gui/icons/") == std::string::npos)) + // Loop through possible file extensions (svg first) + for (auto s : ext) { - std::string tmp_path = file_manager->getAsset(relative_path2); - if (file_manager->fileExists(tmp_path)) + std::string relative_path2 = path_no_extension + s; + if (!hasIconTheme() || + (relative_path2.find("karts/") == std::string::npos && + relative_path2.find("gui/icons/") == std::string::npos)) { - return tmp_path; + std::string tmp_path = file_manager->getAsset(relative_path2); + if (file_manager->fileExists(tmp_path)) + { + return tmp_path; + } + } + + std::string test_path = p + "data/" + relative_path2; + if (file_manager->fileExists(test_path)) + { + return test_path; } } - - std::string test_path = SkinConfig::m_data_path + "data/" + relative_path2; - if (file_manager->fileExists(test_path)) - { - return test_path; - } } - // if nothing found, return the bundled one + // If nothing found, return the bundled icon return file_manager->getAsset(relative_path); } // getThemedIcon