/* * The ManaPlus Client * Copyright (C) 2008 The Legend of Mazzeroth Development Team * Copyright (C) 2009 Aethyra Development Team * Copyright (C) 2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2016 The ManaPlus Developers * * This file is part of The ManaPlus Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "gui/theme.h" #include "configuration.h" #include "graphicsmanager.h" #include "const/gui/theme.h" #include "gui/skin.h" #include "gui/themeinfo.h" #include "resources/image.h" #include "resources/imagerect.h" #include "resources/resourcemanager.h" #include "resources/dye/dyepalette.h" #include "utils/dtor.h" #include "utils/files.h" #include "utils/physfstools.h" #include "debug.h" static std::string defaultThemePath; std::string Theme::mThemePath; std::string Theme::mThemeName; std::string Theme::mScreenDensity; Theme *theme = nullptr; // Set the theme path... static void initDefaultThemePath() { defaultThemePath = branding.getStringValue("guiThemePath"); logger->log("defaultThemePath: " + defaultThemePath); if (!defaultThemePath.empty() && PhysFs::isDirectory( defaultThemePath.c_str())) { return; } else { defaultThemePath = "themes/"; } } Theme::Theme() : Palette(static_cast(ThemeColorId::THEME_COLORS_END) * THEME_PALETTES), mSkins(), mMinimumOpacity(-1.0F), mProgressColors(ProgressColors(static_cast( ProgressColorId::THEME_PROG_END))) { initDefaultThemePath(); config.addListener("guialpha", this); mColors[static_cast(ThemeColorId::HIGHLIGHT)].ch = 'H'; mColors[static_cast(ThemeColorId::CHAT)].ch = 'C'; mColors[static_cast(ThemeColorId::GM)].ch = 'G'; mColors[static_cast(ThemeColorId::GLOBAL)].ch = 'g'; mColors[static_cast(ThemeColorId::PLAYER)].ch = 'Y'; mColors[static_cast(ThemeColorId::WHISPER_TAB)].ch = 'W'; mColors[static_cast(ThemeColorId::WHISPER_TAB_OFFLINE)].ch = 'w'; mColors[static_cast(ThemeColorId::IS)].ch = 'I'; mColors[static_cast(ThemeColorId::PARTY_CHAT_TAB)].ch = 'P'; mColors[static_cast(ThemeColorId::GUILD_CHAT_TAB)].ch = 'U'; mColors[static_cast(ThemeColorId::SERVER)].ch = 'S'; mColors[static_cast(ThemeColorId::LOGGER)].ch = 'L'; mColors[static_cast(ThemeColorId::HYPERLINK)].ch = '<'; mColors[static_cast(ThemeColorId::SELFNICK)].ch = 's'; mColors[static_cast(ThemeColorId::OLDCHAT)].ch = 'o'; mColors[static_cast(ThemeColorId::AWAYCHAT)].ch = 'a'; mCharColors['H'] = static_cast(ThemeColorId::HIGHLIGHT); mCharColors['C'] = static_cast(ThemeColorId::CHAT); mCharColors['G'] = static_cast(ThemeColorId::GM); mCharColors['g'] = static_cast(ThemeColorId::GLOBAL); mCharColors['Y'] = static_cast(ThemeColorId::PLAYER); mCharColors['W'] = static_cast(ThemeColorId::WHISPER_TAB); mCharColors['w'] = static_cast(ThemeColorId::WHISPER_TAB_OFFLINE); mCharColors['I'] = static_cast(ThemeColorId::IS); mCharColors['P'] = static_cast(ThemeColorId::PARTY_CHAT_TAB); mCharColors['U'] = static_cast(ThemeColorId::GUILD_CHAT_TAB); mCharColors['S'] = static_cast(ThemeColorId::SERVER); mCharColors['L'] = static_cast(ThemeColorId::LOGGER); mCharColors['<'] = static_cast(ThemeColorId::HYPERLINK); mCharColors['s'] = static_cast(ThemeColorId::SELFNICK); mCharColors['o'] = static_cast(ThemeColorId::OLDCHAT); mCharColors['a'] = static_cast(ThemeColorId::AWAYCHAT); // here need use outlined colors mCharColors['H' | 0x80] = static_cast(ThemeColorId::HIGHLIGHT_OUTLINE); mCharColors['C' | 0x80] = static_cast(ThemeColorId::CHAT_OUTLINE); mCharColors['G' | 0x80] = static_cast(ThemeColorId::GM_OUTLINE); mCharColors['g' | 0x80] = static_cast(ThemeColorId::GLOBAL_OUTLINE); mCharColors['Y' | 0x80] = static_cast(ThemeColorId::PLAYER_OUTLINE); mCharColors['W' | 0x80] = static_cast(ThemeColorId::WHISPER_TAB_OUTLINE); mCharColors['w' | 0x80] = static_cast(ThemeColorId::WHISPER_TAB_OFFLINE_OUTLINE); mCharColors['I' | 0x80] = static_cast(ThemeColorId::IS_OUTLINE); mCharColors['P' | 0x80] = static_cast(ThemeColorId::PARTY_CHAT_TAB_OUTLINE); mCharColors['U' | 0x80] = static_cast(ThemeColorId::GUILD_CHAT_TAB_OUTLINE); mCharColors['S' | 0x80] = static_cast(ThemeColorId::SERVER_OUTLINE); mCharColors['L' | 0x80] = static_cast(ThemeColorId::LOGGER_OUTLINE); mCharColors['<' | 0x80] = static_cast(ThemeColorId::HYPERLINK_OUTLINE); mCharColors['s' | 0x80] = static_cast(ThemeColorId::SELFNICK_OUTLINE); mCharColors['o' | 0x80] = static_cast(ThemeColorId::OLDCHAT_OUTLINE); mCharColors['a' | 0x80] = static_cast(ThemeColorId::AWAYCHAT_OUTLINE); } Theme::~Theme() { delete_all(mSkins); config.removeListener("guialpha", this); CHECKLISTENERS delete_all(mProgressColors); } Color Theme::getProgressColor(const ProgressColorIdT type, const float progress) { int color[3] = {0, 0, 0}; if (theme) { const DyePalette *const dye = theme->mProgressColors[static_cast(type)]; if (dye) { dye->getColor(progress, color); } else { logger->log("color not found: " + toString(static_cast(type))); } } return Color(color[0], color[1], color[2]); } Skin *Theme::load(const std::string &filename, const std::string &filename2, const bool full, const std::string &restrict defaultPath) { // Check if this skin was already loaded const SkinIterator skinIterator = mSkins.find(filename); if (mSkins.end() != skinIterator) { if (skinIterator->second) skinIterator->second->instances++; return skinIterator->second; } Skin *skin = nullptr; if (mScreenDensity.empty()) { // if no density detected skin = readSkin(filename, full); if (!skin && !filename2.empty() && filename2 != filename) skin = readSkin(filename2, full); if (!skin && filename2 != "window.xml") skin = readSkin("window.xml", full); } else { // first use correct density images const std::string endStr("_" + mScreenDensity + ".xml"); std::string name = filename; if (findCutLast(name, ".xml")) skin = readSkin(name + endStr, full); if (!skin) skin = readSkin(filename, full); if (!skin && !filename2.empty() && filename2 != filename) { name = filename2; if (findCutLast(name, ".xml")) skin = readSkin(name + endStr, full); if (!skin) skin = readSkin(filename2, full); } if (!skin && filename2 != "window.xml") { skin = readSkin("window" + endStr, full); if (!skin) skin = readSkin("window.xml", full); } } if (!skin) { // Try falling back on the defaultPath if this makes sense if (filename != defaultPath) { logger->log("Error loading skin '%s', falling back on default.", filename.c_str()); skin = readSkin(defaultPath, full); } if (!skin) { logger->log(strprintf("Error: Loading default skin '%s' failed. " "Make sure the skin file is valid.", defaultPath.c_str())); } } mSkins[filename] = skin; return skin; } void Theme::unload(Skin *const skin) { if (!skin) return; skin->instances --; if (!skin->instances) { SkinIterator it = mSkins.begin(); const SkinIterator it_end = mSkins.end(); while (it != it_end) { if (it->second == skin) { mSkins.erase(it); break; } ++ it; } delete skin; } } void Theme::setMinimumOpacity(const float minimumOpacity) { if (minimumOpacity > 1.0F) return; mMinimumOpacity = minimumOpacity; updateAlpha(); } void Theme::updateAlpha() { FOR_EACH (SkinIterator, iter, mSkins) { Skin *const skin = iter->second; if (skin) skin->updateAlpha(mMinimumOpacity); } } void Theme::optionChanged(const std::string &) { updateAlpha(); } struct SkinParameter final { int index; std::string name; }; static const SkinParameter skinParam[] = { {0, "top-left-corner"}, {0, "standart"}, {0, "up"}, {0, "hstart"}, {0, "in"}, {0, "normal"}, {1, "top-edge"}, {1, "highlighted"}, {1, "down"}, {1, "hmiddle"}, {1, "in-highlighted"}, {1, "checked"}, {2, "top-right-corner"}, {2, "pressed"}, {2, "left"}, {2, "hend"}, {2, "out"}, {2, "disabled"}, {3, "left-edge"}, {3, "disabled"}, {3, "right"}, {3, "hgrip"}, {3, "out-highlighted"}, {3, "disabled-checked"}, {4, "bg-quad"}, {4, "vstart"}, {4, "normal-highlighted"}, {5, "right-edge"}, {5, "vmiddle"}, {5, "checked-highlighted"}, {6, "bottom-left-corner"}, {6, "vend"}, {7, "bottom-edge"}, {7, "vgrip"}, {8, "bottom-right-corner"}, }; static const SkinParameter imageParam[] = { {0, "closeImage"}, {1, "closeImageHighlighted"}, {2, "stickyImageUp"}, {3, "stickyImageDown"}, }; struct SkinHelper final { SkinHelper() : partType(), xPos(), yPos(), width(), height(), rect(), node(), image() { } A_DELETE_COPY(SkinHelper) std::string partType; int xPos; int yPos; int width; int height; ImageRect *rect; XmlNodePtr *node; Image *image; bool loadList(const SkinParameter *const params, const size_t size) A_NONNULL(2) { for (size_t f = 0; f < size; f ++) { const SkinParameter ¶m = params[f]; if (partType == param.name) { rect->grid[param.index] = resourceManager->getSubImage( image, xPos, yPos, width, height); return true; } } return false; } }; Skin *Theme::readSkin(const std::string &filename, const bool full) { if (filename.empty()) return nullptr; // logger->log("Loading skin '%s'.", filename.c_str()); XML::Document doc(resolveThemePath(filename), UseResman_true, SkipError_true); const XmlNodePtr rootNode = doc.rootNode(); if (!rootNode || !xmlNameEqual(rootNode, "skinset")) return nullptr; const std::string skinSetImage = XML::getProperty(rootNode, "image", ""); if (skinSetImage.empty()) { logger->log1("Theme::readSkin(): Skinset does not define an image!"); return nullptr; } Image *const dBorders = Theme::getImageFromTheme(skinSetImage); ImageRect *const border = new ImageRect; ImageRect *const images = new ImageRect; memset(border, 0, sizeof(ImageRect)); memset(images, 0, sizeof(ImageRect)); int padding = 3; int titlePadding = 4; int titlebarHeight = 0; int titlebarHeightRelative = 0; int closePadding = 3; int stickySpacing = 3; int stickyPadding = 3; int resizePadding = 2; StringIntMap *const mOptions = new StringIntMap; // iterate 's for_each_xml_child_node(widgetNode, rootNode) { if (!xmlNameEqual(widgetNode, "widget")) continue; const std::string widgetType = XML::getProperty(widgetNode, "type", "unknown"); if (widgetType == "Window") { SkinHelper helper; const int globalXPos = XML::getProperty(widgetNode, "xpos", 0); const int globalYPos = XML::getProperty(widgetNode, "ypos", 0); for_each_xml_child_node(partNode, widgetNode) { if (xmlNameEqual(partNode, "part")) { helper.partType = XML::getProperty( partNode, "type", "unknown"); helper.xPos = XML::getProperty( partNode, "xpos", 0) + globalXPos; helper.yPos = XML::getProperty( partNode, "ypos", 0) + globalYPos; helper.width = XML::getProperty(partNode, "width", 0); helper.height = XML::getProperty(partNode, "height", 0); if (!helper.width || !helper.height) continue; helper.image = dBorders; helper.rect = border; if (!helper.loadList(skinParam, sizeof(skinParam) / sizeof(SkinParameter))) { helper.rect = images; helper.loadList(imageParam, sizeof(imageParam) / sizeof(SkinParameter)); } } else if (full && xmlNameEqual(partNode, "option")) { const std::string name = XML::getProperty( partNode, "name", ""); if (name == "padding") { padding = XML::getProperty(partNode, "value", 3); } else if (name == "titlePadding") { titlePadding = XML::getProperty(partNode, "value", 4); } else if (name == "closePadding") { closePadding = XML::getProperty(partNode, "value", 3); } else if (name == "stickySpacing") { stickySpacing = XML::getProperty(partNode, "value", 3); } else if (name == "stickyPadding") { stickyPadding = XML::getProperty(partNode, "value", 3); } else if (name == "titlebarHeight") { titlebarHeight = XML::getProperty( partNode, "value", 0); } else if (name == "titlebarHeightRelative") { titlebarHeightRelative = XML::getProperty( partNode, "value", 0); } else if (name == "resizePadding") { resizePadding = XML::getProperty( partNode, "value", 2); } else { (*mOptions)[name] = XML::getProperty( partNode, "value", 0); } } } } else { logger->log("Theme::readSkin(): Unknown widget type '%s'", widgetType.c_str()); } } if (dBorders) dBorders->decRef(); (*mOptions)["closePadding"] = closePadding; (*mOptions)["stickyPadding"] = stickyPadding; (*mOptions)["stickySpacing"] = stickySpacing; (*mOptions)["titlebarHeight"] = titlebarHeight; (*mOptions)["titlebarHeightRelative"] = titlebarHeightRelative; (*mOptions)["resizePadding"] = resizePadding; Skin *const skin = new Skin(border, images, filename, "", padding, titlePadding, mOptions); delete images; skin->updateAlpha(mMinimumOpacity); return skin; } bool Theme::tryThemePath(const std::string &themeName) { if (!themeName.empty()) { const std::string path = defaultThemePath + themeName; if (PhysFs::exists(path.c_str())) { mThemePath = path; mThemeName = themeName; if (theme) theme->loadColors(""); return true; } } return false; } void Theme::fillSkinsList(StringVect &list) { Files::getDirs(branding.getStringValue("guiThemePath"), list); } void Theme::fillFontsList(StringVect &list) { PHYSFS_permitSymbolicLinks(1); Files::getFiles(branding.getStringValue("fontsPath"), list); PHYSFS_permitSymbolicLinks(0); } void Theme::fillSoundsList(StringVect &list) { char **skins = PhysFs::enumerateFiles( branding.getStringValue("systemsounds").c_str()); for (char **i = skins; *i; i++) { if (!PhysFs::isDirectory(( branding.getStringValue("systemsounds") + *i).c_str())) { std::string str = *i; if (findCutLast(str, ".ogg")) list.push_back(str); } } PhysFs::freeList(skins); } void Theme::selectSkin() { prepareThemePath(); mScreenDensity = graphicsManager.getDensityString(); } void Theme::prepareThemePath() { initDefaultThemePath(); mThemePath.clear(); mThemeName.clear(); // Try theme from settings if (tryThemePath(config.getStringValue("theme"))) return; // Try theme from branding if (tryThemePath(branding.getStringValue("theme"))) return; if (mThemePath.empty()) mThemePath = "graphics/gui"; theme->loadColors(mThemePath); logger->log("Selected Theme: " + mThemePath); } std::string Theme::resolveThemePath(const std::string &path) { // Need to strip off any dye info for the existence tests const int pos = static_cast(path.find('|')); std::string file; if (pos > 0) file = path.substr(0, pos); else file = path; // File with path if (file.find('/') != std::string::npos) { // Might be a valid path already if (PhysFs::exists(file.c_str())) return path; } // Try the theme file = getThemePath().append("/").append(file); if (PhysFs::exists(file.c_str())) return getThemePath().append("/").append(path); // Backup return branding.getStringValue("guiPath").append(path); } Image *Theme::getImageFromTheme(const std::string &path) { return resourceManager->getImage(resolveThemePath(path)); } ImageSet *Theme::getImageSetFromTheme(const std::string &path, const int w, const int h) { return resourceManager->getImageSet(resolveThemePath(path), w, h); } static int readColorType(const std::string &type) { static const std::string colors[static_cast( ThemeColorId::THEME_COLORS_END)] = { "BROWSERBOX", "BROWSERBOX_OUTLINE", "SELFNICK", "SELFNICK_OUTLINE", "TEXT", "TEXT_OUTLINE", "CARET", "SHADOW", "OUTLINE", "BORDER", "PROGRESS_BAR", "PROGRESS_BAR_OUTLINE", "BUTTON", "BUTTON_OUTLINE", "BUTTON_DISABLED", "BUTTON_DISABLED_OUTLINE", "BUTTON_HIGHLIGHTED", "BUTTON_HIGHLIGHTED_OUTLINE", "BUTTON_PRESSED", "BUTTON_PRESSED_OUTLINE", "CHECKBOX", "CHECKBOX_OUTLINE", "DROPDOWN", "DROPDOWN_OUTLINE", "LABEL", "LABEL_OUTLINE", "LISTBOX", "LISTBOX_OUTLINE", "LISTBOX_SELECTED", "LISTBOX_SELECTED_OUTLINE", "RADIOBUTTON", "RADIOBUTTON_OUTLINE", "POPUP", "POPUP_OUTLINE", "TAB", "TAB_OUTLINE", "TAB_HIGHLIGHTED", "TAB_HIGHLIGHTED_OUTLINE", "TAB_SELECTED", "TAB_SELECTED_OUTLINE", "TEXTBOX", "TEXTFIELD", "TEXTFIELD_OUTLINE", "WINDOW", "WINDOW_OUTLINE", "BATTLE_CHAT_TAB", "BATTLE_CHAT_TAB_OUTLINE", "CHANNEL_CHAT_TAB", "CHANNEL_CHAT_TAB_OUTLINE", "PARTY_CHAT_TAB", "PARTY_CHAT_TAB_OUTLINE", "PARTY_SOCIAL_TAB", "PARTY_SOCIAL_TAB_OUTLINE", "GUILD_CHAT_TAB", "GUILD_CHAT_TAB_OUTLINE", "GUILD_SOCIAL_TAB", "GUILD_SOCIAL_TAB_OUTLINE", "GM_CHAT_TAB", "GM_CHAT_TAB_OUTLINE", "BATTLE_CHAT_TAB_HIGHLIGHTED", "BATTLE_CHAT_TAB_HIGHLIGHTED_OUTLINE", "CHANNEL_CHAT_TAB_HIGHLIGHTED", "CHANNEL_CHAT_TAB_HIGHLIGHTED_OUTLINE", "PARTY_CHAT_TAB_HIGHLIGHTED", "PARTY_CHAT_TAB_HIGHLIGHTED_OUTLINE", "PARTY_SOCIAL_TAB_HIGHLIGHTED", "PARTY_SOCIAL_TAB_HIGHLIGHTED_OUTLINE", "GUILD_CHAT_TAB_HIGHLIGHTED", "GUILD_CHAT_TAB_HIGHLIGHTED_OUTLINE", "GUILD_SOCIAL_TAB_HIGHLIGHTED", "GUILD_SOCIAL_TAB_HIGHLIGHTED_OUTLINE", "GM_CHAT_TAB_HIGHLIGHTED", "GM_CHAT_TAB_HIGHLIGHTED_OUTLINE", "BATTLE_CHAT_TAB_SELECTED", "BATTLE_CHAT_TAB_SELECTED_OUTLINE", "CHANNEL_CHAT_TAB_SELECTED", "CHANNEL_CHAT_TAB_SELECTED_OUTLINE", "PARTY_CHAT_TAB_SELECTED", "PARTY_CHAT_TAB_SELECTED_OUTLINE", "PARTY_SOCIAL_TAB_SELECTED", "PARTY_SOCIAL_TAB_SELECTED_OUTLINE", "GUILD_CHAT_TAB_SELECTED", "GUILD_CHAT_TAB_SELECTED_OUTLINE", "GUILD_SOCIAL_TAB_SELECTED", "GUILD_SOCIAL_TAB_SELECTED_OUTLINE", "GM_CHAT_TAB_SELECTED", "GM_CHAT_TAB_SELECTED_OUTLINE", "BACKGROUND", "BACKGROUND_GRAY", "SCROLLBAR_GRAY", "DROPDOWN_SHADOW", "HIGHLIGHT", "HIGHLIGHT_OUTLINE", "TAB_FLASH", "TAB_FLASH_OUTLINE", "TAB_PLAYER_FLASH", "TAB_PLAYER_FLASH_OUTLINE", "SHOP_WARNING", "ITEM_EQUIPPED", "ITEM_EQUIPPED_OUTLINE", "ITEM_NOT_EQUIPPED", "ITEM_NOT_EQUIPPED_OUTLINE", "CHAT", "CHAT_OUTLINE", "GM", "GM_OUTLINE", "GLOBAL", "GLOBAL_OUTLINE", "PLAYER", "PLAYER_OUTLINE", "WHISPER_TAB", "WHISPER_TAB_OUTLINE", "WHISPER_TAB_OFFLINE", "WHISPER_TAB_OFFLINE_OUTLINE", "WHISPER_TAB_HIGHLIGHTED", "WHISPER_TAB_HIGHLIGHTED_OUTLINE", "WHISPER_TAB_OFFLINE_HIGHLIGHTED", "WHISPER_TAB_OFFLINE_HIGHLIGHTED_OUTLINE", "WHISPER_TAB_SELECTED", "WHISPER_TAB_SELECTED_OUTLINE", "WHISPER_TAB_OFFLINE_SELECTED", "WHISPER_TAB_OFFLINE_SELECTED_OUTLINE", "IS", "IS_OUTLINE", "SERVER", "SERVER_OUTLINE", "LOGGER", "LOGGER_OUTLINE", "HYPERLINK", "HYPERLINK_OUTLINE", "UNKNOWN_ITEM", "UNKNOWN_ITEM_OUTLINE", "GENERIC", "GENERIC_OUTLINE", "HEAD", "HEAD_OUTLINE", "USABLE", "USABLE_OUTLINE", "TORSO", "TORSO_OUTLINE", "ONEHAND", "ONEHAND_OUTLINE", "LEGS", "LEGS_OUTLINE", "FEET", "FEET_OUTLINE", "TWOHAND", "TWOHAND_OUTLINE", "SHIELD", "SHIELD_OUTLINE", "RING", "RING_OUTLINE", "NECKLACE", "NECKLACE_OUTLINE", "ARMS", "ARMS_OUTLINE", "AMMO", "AMMO_OUTLINE", "SERVER_VERSION_NOT_SUPPORTED", "SERVER_VERSION_NOT_SUPPORTED_OUTLINE", "WARNING", "WARNING_OUTLINE", "CHARM", "CHARM_OUTLINE", "CARD", "CARD_OUTLINE", "PLAYER_ADVANCED", "PLAYER_ADVANCED_OUTLINE", "BUBBLE_NAME", "BUBBLE_NAME_OUTLINE", "BUBBLE_TEXT", "BUBBLE_TEXT_OUTLINE", "BLACK", "BLACK_OUTLINE", "RED", "RED_OUTLINE", "GREEN", "GREEN_OUTLINE", "BLUE", "BLUE_OUTLINE", "ORANGE", "ORANGE_OUTLINE", "YELLOW", "YELLOW_OUTLINE", "PINK", "PINK_OUTLINE", "PURPLE", "PURPLE_OUTLINE", "GRAY", "GRAY_OUTLINE", "BROWN", "BROWN_OUTLINE", "STATUSBAR_ON", "STATUSBAR_OFF", "TABLE_BACKGROUND", "SLOTS_BAR", "SLOTS_BAR_OUTLINE", "HP_BAR", "HP_BAR_OUTLINE", "MP_BAR", "MP_BAR_OUTLINE", "NO_MP_BAR", "NO_MP_BAR_OUTLINE", "XP_BAR", "XP_BAR_OUTLINE", "WEIGHT_BAR", "WEIGHT_BAR_OUTLINE", "MONEY_BAR", "MONEY_BAR_OUTLINE", "ARROWS_BAR", "ARROWS_BAR_OUTLINE", "STATUS_BAR", "STATUS_BAR_OUTLINE", "JOB_BAR", "JOB_BAR_OUTLINE", "OLDCHAT", "OLDCHAT_OUTLINE", "AWAYCHAT", "AWAYCHAT_OUTLINE", "SKILL_COOLDOWN", "TEXT_DISABLED", "TEXT_DISABLED_OUTLINE" }; if (type.empty()) return -1; for (int i = 0; i < static_cast(ThemeColorId::THEME_COLORS_END); i++) { if (compareStrI(type, colors[i]) == 0) return i; } return -1; } static Color readColor(const std::string &description) { const int size = static_cast(description.length()); if (size < 7 || description[0] != '#') { logger->log("Error, invalid theme color palette: %s", description.c_str()); return Palette::BLACK; } unsigned int v = 0; for (int i = 1; i < 7; ++i) { signed const char c = description[i]; int n; if ('0' <= c && c <= '9') { n = c - '0'; } else if ('A' <= c && c <= 'F') { n = c - 'A' + 10; } else if ('a' <= c && c <= 'f') { n = c - 'a' + 10; } else { logger->log("Error, invalid theme color palette: %s", description.c_str()); return Palette::BLACK; } v = (v << 4) | n; } return Color(v); } static GradientTypeT readColorGradient(const std::string &grad) { static const std::string grads[] = { "STATIC", "PULSE", "SPECTRUM", "RAINBOW" }; if (grad.empty()) return GradientType::STATIC; for (int i = 0; i < 4; i++) { if (compareStrI(grad, grads[i])) return static_cast(i); } return GradientType::STATIC; } static int readProgressType(const std::string &type) { static const std::string colors[static_cast( ProgressColorId::THEME_PROG_END)] = { "HP", "HP_POISON", "MP", "NO_MP", "EXP", "INVY_SLOTS", "WEIGHT", "JOB", "UPDATE", "MONEY", "ARROWS", "STATUS" }; if (type.empty()) return -1; for (int i = 0; i < static_cast(ProgressColorId::THEME_PROG_END); i++) { if (compareStrI(type, colors[i]) == 0) return i; } return -1; } void Theme::loadColors(std::string file) { if (file == "") file = "colors.xml"; else file.append("/colors.xml"); XML::Document doc(resolveThemePath(file), UseResman_true, SkipError_false); const XmlNodePtrConst root = doc.rootNode(); if (!root || !xmlNameEqual(root, "colors")) { logger->log("Error loading colors file: %s", file.c_str()); return; } logger->log("Loading colors file: %s", file.c_str()); for_each_xml_child_node(paletteNode, root) { if (xmlNameEqual(paletteNode, "progressbar")) { const int type = readProgressType(XML::getProperty( paletteNode, "id", "")); if (type < 0) continue; mProgressColors[type] = new DyePalette(XML::getProperty( paletteNode, "color", ""), 6); } else if (!xmlNameEqual(paletteNode, "palette")) { continue; } const int paletteId = XML::getProperty(paletteNode, "id", 1); if (paletteId < 0 || paletteId >= THEME_PALETTES) continue; for_each_xml_child_node(node, paletteNode) { if (xmlNameEqual(node, "color")) { const std::string id = XML::getProperty(node, "id", ""); const int type = readColorType(id); if (type < 0) continue; const std::string temp = XML::getProperty(node, "color", ""); if (temp.empty()) continue; const Color color = readColor(temp); const GradientTypeT grad = readColorGradient( XML::getProperty(node, "effect", "")); mColors[paletteId * static_cast( ThemeColorId::THEME_COLORS_END) + type].set( type, color, grad, 10); if (!findLast(id, "_OUTLINE")) { const int type2 = readColorType(id + "_OUTLINE"); if (type2 < 0) continue; const int idx = paletteId * static_cast(ThemeColorId::THEME_COLORS_END); mColors[idx + type2] = mColors[idx + type]; } } } } } #define loadGrid() \ { \ const ImageRect &rect = skin->getBorder(); \ for (int f = start; f <= end; f ++) \ { \ if (rect.grid[f]) \ { \ image.grid[f] = rect.grid[f]; \ image.grid[f]->incRef(); \ } \ } \ } void Theme::loadRect(ImageRect &image, const std::string &name, const std::string &name2, const int start, const int end) { Skin *const skin = load(name, name2, false); if (skin) { loadGrid(); unload(skin); } } Skin *Theme::loadSkinRect(ImageRect &image, const std::string &name, const std::string &name2, const int start, const int end) { Skin *const skin = load(name, name2); if (skin) loadGrid(); return skin; } void Theme::unloadRect(const ImageRect &rect, const int start, const int end) { for (int f = start; f <= end; f ++) { if (rect.grid[f]) rect.grid[f]->decRef(); } } Image *Theme::getImageFromThemeXml(const std::string &name, const std::string &name2) { if (!theme) return nullptr; Skin *const skin = theme->load(name, name2, false); if (skin) { const ImageRect &rect = skin->getBorder(); if (rect.grid[0]) { Image *const image = rect.grid[0]; image->incRef(); theme->unload(skin); return image; } theme->unload(skin); } return nullptr; } ImageSet *Theme::getImageSetFromThemeXml(const std::string &name, const std::string &name2, const int w, const int h) { if (!theme) return nullptr; Skin *const skin = theme->load(name, name2, false); if (skin) { const ImageRect &rect = skin->getBorder(); if (rect.grid[0]) { Image *const image = rect.grid[0]; const SDL_Rect &rect2 = image->mBounds; if (rect2.w && rect2.h) { ImageSet *const imageSet = resourceManager->getSubImageSet( image, w, h); theme->unload(skin); return imageSet; } } theme->unload(skin); } return nullptr; } #define readValue(name) \ info->name = reinterpret_cast(\ XmlNodeGetContent(infoNode)) #define readIntValue(name) \ info->name = atoi(reinterpret_cast(\ XmlNodeGetContent(infoNode))) #define readFloatValue(name) \ info->name = static_cast(atof(reinterpret_cast(\ XmlNodeGetContent(infoNode)))) ThemeInfo *Theme::loadInfo(const std::string &themeName) { std::string path; if (themeName.empty()) { path = "graphics/gui/info.xml"; } else { path = std::string(defaultThemePath).append( themeName).append("/info.xml"); } logger->log("loading: " + path); XML::Document doc(path, UseResman_true, SkipError_false); const XmlNodePtrConst rootNode = doc.rootNode(); if (!rootNode || !xmlNameEqual(rootNode, "info")) return nullptr; ThemeInfo *const info = new ThemeInfo(); const std::string fontSize2("fontSize_" + mScreenDensity); const std::string npcfontSize2("npcfontSize_" + mScreenDensity); for_each_xml_child_node(infoNode, rootNode) { if (xmlNameEqual(infoNode, "name")) readValue(name); else if (xmlNameEqual(infoNode, "copyright")) readValue(copyright); else if (xmlNameEqual(infoNode, "font")) readValue(font); else if (xmlNameEqual(infoNode, "boldFont")) readValue(boldFont); else if (xmlNameEqual(infoNode, "particleFont")) readValue(particleFont); else if (xmlNameEqual(infoNode, "helpFont")) readValue(helpFont); else if (xmlNameEqual(infoNode, "secureFont")) readValue(secureFont); else if (xmlNameEqual(infoNode, "npcFont")) readValue(npcFont); else if (xmlNameEqual(infoNode, "japanFont")) readValue(japanFont); else if (xmlNameEqual(infoNode, "chinaFont")) readValue(chinaFont); else if (xmlNameEqual(infoNode, "fontSize")) readIntValue(fontSize); else if (xmlNameEqual(infoNode, "npcfontSize")) readIntValue(npcfontSize); else if (xmlNameEqual(infoNode, "guialpha")) readFloatValue(guiAlpha); else if (xmlNameEqual(infoNode, fontSize2.c_str())) readIntValue(fontSize); else if (xmlNameEqual(infoNode, npcfontSize2.c_str())) readIntValue(npcfontSize); } return info; } ThemeColorIdT Theme::getIdByChar(const signed char c, bool &valid) const { const CharColors::const_iterator it = mCharColors.find(c); if (it != mCharColors.end()) { valid = true; return static_cast((*it).second); } valid = false; return ThemeColorId::BROWSERBOX; }