diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-04-11 12:17:56 +0200 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-04-25 14:05:30 +0000 |
commit | 243b6c163cd91b874265d5acee57e754dec2e979 (patch) | |
tree | b64c57272e408eabe375957bb4d4b05df5aa48fc | |
parent | ca29443d65152fde5ad960f5c02b5fc0f2dd378f (diff) | |
download | mana-243b6c163cd91b874265d5acee57e754dec2e979.tar.gz mana-243b6c163cd91b874265d5acee57e754dec2e979.tar.bz2 mana-243b6c163cd91b874265d5acee57e754dec2e979.tar.xz mana-243b6c163cd91b874265d5acee57e754dec2e979.zip |
Introduced theme dropdown in interface setup
* Added Theme dropdown to Interface setup
* Added CARET theme color
* Fixed issue with logging errors in `check` function in `theme.cpp`
* Fixed XML::Children::Iterator to iterate only element nodes
* Changed default theme to "jewelry"
Changing the theme (or font size) shows a dialog that points out a restart is
required to apply these changes. This is necessary at the moment because many
things, like default or minimum window sizes, are only calculated once.
-rw-r--r-- | data/graphics/gui/jewelry/theme.xml | 4 | ||||
-rw-r--r-- | data/graphics/gui/theme.xml | 1 | ||||
-rw-r--r-- | src/configuration.h | 2 | ||||
-rw-r--r-- | src/gui/gui.cpp | 20 | ||||
-rw-r--r-- | src/gui/gui.h | 12 | ||||
-rw-r--r-- | src/gui/setup_interface.cpp | 92 | ||||
-rw-r--r-- | src/gui/setup_interface.h | 11 | ||||
-rw-r--r-- | src/gui/setup_video.cpp | 2 | ||||
-rw-r--r-- | src/gui/widgets/scrollarea.cpp | 24 | ||||
-rw-r--r-- | src/gui/widgets/scrollarea.h | 7 | ||||
-rw-r--r-- | src/gui/widgets/textfield.cpp | 2 | ||||
-rw-r--r-- | src/log.cpp | 11 | ||||
-rw-r--r-- | src/log.h | 2 | ||||
-rw-r--r-- | src/resources/theme.cpp | 151 | ||||
-rw-r--r-- | src/resources/theme.h | 29 | ||||
-rw-r--r-- | src/utils/xml.h | 13 |
16 files changed, 258 insertions, 125 deletions
diff --git a/data/graphics/gui/jewelry/theme.xml b/data/graphics/gui/jewelry/theme.xml index 15db87fb..0d11f61a 100644 --- a/data/graphics/gui/jewelry/theme.xml +++ b/data/graphics/gui/jewelry/theme.xml @@ -9,7 +9,6 @@ <color id="BORDER" color="#000000" /> <color id="CHECKBOX" color="#000000" /> <color id="DROPDOWN" color="#000000" /> - <color id="LABEL" color="#000000" /> <color id="LISTBOX" color="#000000" /> <color id="LISTBOX_SELECTED" color="#000000" /> <color id="RADIOBUTTON" color="#000000" /> @@ -26,11 +25,8 @@ <color id="CHANNEL_CHAT_TAB_HIGHLIGHTED_OUTLINE" color="#63450a" /> <color id="CHANNEL_CHAT_TAB_SELECTED" color="#f8eacf" /> <color id="CHANNEL_CHAT_TAB_SELECTED_OUTLINE" color="#63450a" /> - <color id="TEXTBOX" color="#000000" /> - <color id="TEXTFIELD" color="#000000" /> <color id="BACKGROUND" color="#ffffff" /> <color id="BACKGROUND_GRAY" color="#404040" /> - <color id="SCROLLBAR_GRAY" color="#000000" /> <color id="DROPDOWN_SHADOW" color="#c0c0c0" /> <color id="HIGHLIGHT" color="#ebc873" /> <color id="TAB_FLASH" color="#fec4ff" effect="pulse" /> diff --git a/data/graphics/gui/theme.xml b/data/graphics/gui/theme.xml index 8bb95f80..eb22fad8 100644 --- a/data/graphics/gui/theme.xml +++ b/data/graphics/gui/theme.xml @@ -1,5 +1,6 @@ <theme name="Mana"> <color id="TEXT" color="#000000" /> + <color id="CARET" color="#000000" /> <color id="SHADOW" color="#000000" /> <color id="OUTLINE" color="#000000" /> <color id="PARTY_CHAT_TAB" color="#f48055" /> diff --git a/src/configuration.h b/src/configuration.h index 510ac269..3ed3c77e 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -233,7 +233,7 @@ struct Config int scrollCenterOffsetX = 0; int scrollCenterOffsetY = 0; std::string onlineServerList; - std::string theme; + std::string theme = "jewelry"; bool disableTransparency = false; bool persistentPlayerList = true; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d72bd56d..0c3d78eb 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -42,6 +42,8 @@ #include <guichan/exception.hpp> #include <guichan/image.hpp> +#include <algorithm> + #include <SDL_image.h> // Guichan stuff @@ -57,9 +59,18 @@ gcn::Font *monoFont = nullptr; bool Gui::debugDraw; Gui::Gui(Graphics *graphics, const std::string &themePath) - : mTheme(new Theme(themePath)) + : mAvailableThemes(Theme::getAvailableThemes()) , mCustomCursorScale(Client::getVideo().settings().scale()) { + // Try to find the requested theme, using the first one as fallback + auto themeIt = std::find_if(mAvailableThemes.begin(), + mAvailableThemes.end(), + [&themePath](const ThemeInfo &theme) { + return theme.getPath() == themePath; + }); + + setTheme(themeIt != mAvailableThemes.end() ? *themeIt : mAvailableThemes.front()); + logger->log("Initializing GUI..."); // Set graphics setGraphics(graphics); @@ -68,7 +79,7 @@ Gui::Gui(Graphics *graphics, const std::string &themePath) guiInput = new SDLInput; setInput(guiInput); - // Set focus handler + // Replace focus handler delete mFocusHandler; mFocusHandler = new FocusHandler; @@ -226,6 +237,11 @@ void Gui::setCursorType(Cursor cursor) updateCursor(); } +void Gui::setTheme(const ThemeInfo &theme) +{ + mTheme = std::make_unique<Theme>(theme); +} + void Gui::updateCursor() { if (mCustomCursor && !mCustomMouseCursors.empty()) diff --git a/src/gui/gui.h b/src/gui/gui.h index 450514f5..2584b780 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -24,6 +24,8 @@ #include "eventlistener.h" #include "guichanfwd.h" +#include "resources/theme.h" + #include "utils/time.h" #include <guichan/gui.hpp> @@ -33,7 +35,6 @@ #include <memory> #include <vector> -class Theme; class TextInput; class Graphics; class SDLInput; @@ -123,6 +124,14 @@ class Gui final : public gcn::Gui, public EventListener */ void setCursorType(Cursor cursor); + const std::vector<ThemeInfo> &getAvailableThemes() const + { return mAvailableThemes; } + + /** + * Sets the global GUI theme. + */ + void setTheme(const ThemeInfo &theme); + /** * The global GUI theme. */ @@ -141,6 +150,7 @@ class Gui final : public gcn::Gui, public EventListener void loadCustomCursors(); void loadSystemCursors(); + std::vector<ThemeInfo> mAvailableThemes; std::unique_ptr<Theme> mTheme; /**< The global GUI theme */ gcn::Font *mGuiFont; /**< The global GUI font */ gcn::Font *mInfoParticleFont; /**< Font for Info Particles*/ diff --git a/src/gui/setup_interface.cpp b/src/gui/setup_interface.cpp index 7eef974c..ce77d3e6 100644 --- a/src/gui/setup_interface.cpp +++ b/src/gui/setup_interface.cpp @@ -23,12 +23,15 @@ #include "configuration.h" +#include "gui/okdialog.h" #include "gui/widgets/checkbox.h" +#include "gui/widgets/dropdown.h" #include "gui/widgets/label.h" #include "gui/widgets/layout.h" #include "gui/widgets/slider.h" #include "gui/widgets/spacer.h" -#include "gui/widgets/dropdown.h" + +#include "resources/theme.h" #include "utils/gettext.h" @@ -37,8 +40,35 @@ #include <SDL.h> +#include <algorithm> #include <string> +class ThemesListModel : public gcn::ListModel +{ +public: + int getNumberOfElements() override + { + return gui->getAvailableThemes().size(); + } + + std::string getElementAt(int i) override + { + return gui->getAvailableThemes().at(i).getName(); + } + + static int getThemeIndex(const std::string &path) + { + auto &themes = gui->getAvailableThemes(); + auto themeIt = std::find_if(themes.begin(), + themes.end(), + [&](const ThemeInfo &theme) { + return theme.getPath() == path; + }); + return themeIt != themes.end() ? std::distance(themes.begin(), themeIt) : 0; + } +}; + + const char *SIZE_NAME[4] = { N_("Tiny"), @@ -97,8 +127,7 @@ Setup_Interface::Setup_Interface(): mPickupParticleEnabled)), mSpeechSlider(new Slider(0, 3)), mSpeechLabel(new Label(std::string())), - mAlphaSlider(new Slider(0.2, 1.0)), - mFontSize(config.fontSize) + mAlphaSlider(new Slider(0.2, 1.0)) { setName(_("Interface")); @@ -108,18 +137,21 @@ Setup_Interface::Setup_Interface(): mShowMonsterDamageCheckBox = new CheckBox(_("Show damage"), mShowMonsterDamageEnabled); - speechLabel = new Label(_("Overhead text:")); - alphaLabel = new Label(_("GUI opacity")); - fontSizeLabel = new Label(_("Font size:")); + gcn::Label *speechLabel = new Label(_("Overhead text:")); + gcn::Label *alphaLabel = new Label(_("GUI opacity")); + gcn::Label *themeLabel = new Label(_("Theme:")); + gcn::Label *fontSizeLabel = new Label(_("Font size:")); + + mThemesListModel = std::make_unique<ThemesListModel>(); + mThemeDropDown = new DropDown(mThemesListModel.get()); - mFontSizeListModel = new FontSizeChoiceListModel; - mFontSizeDropDown = new DropDown(mFontSizeListModel); + mFontSizeListModel = std::make_unique<FontSizeChoiceListModel>(); + mFontSizeDropDown = new DropDown(mFontSizeListModel.get()); mAlphaSlider->setValue(mOpacity); mAlphaSlider->setWidth(90); mAlphaSlider->setEnabled(!config.disableTransparency); - // Set actions mShowMonsterDamageCheckBox->setActionEventId("monsterdamage"); mVisibleNamesCheckBox->setActionEventId("visiblenames"); @@ -127,6 +159,7 @@ Setup_Interface::Setup_Interface(): mPickupParticleCheckBox->setActionEventId("pickupparticle"); mNameCheckBox->setActionEventId("showownname"); mNPCLogCheckBox->setActionEventId("lognpc"); + mThemeDropDown->setActionEventId("theme"); mAlphaSlider->setActionEventId("guialpha"); mSpeechSlider->setActionEventId("speech"); @@ -137,13 +170,16 @@ Setup_Interface::Setup_Interface(): mPickupParticleCheckBox->addActionListener(this); mNameCheckBox->addActionListener(this); mNPCLogCheckBox->addActionListener(this); + mThemeDropDown->addActionListener(this); mAlphaSlider->addActionListener(this); mSpeechSlider->addActionListener(this); mSpeechLabel->setCaption(speechModeToString(mSpeechMode)); mSpeechSlider->setValue(mSpeechMode); - mFontSizeDropDown->setSelected(mFontSize - 10); + mThemeDropDown->setSelected(ThemesListModel::getThemeIndex(config.theme)); + + mFontSizeDropDown->setSelected(config.fontSize - 10); mFontSizeDropDown->adjustHeight(); // Do the layout @@ -162,27 +198,35 @@ Setup_Interface::Setup_Interface(): place(0, 5, space, 1, 1); - place(0, 6, fontSizeLabel, 2); - place(2, 6, mFontSizeDropDown, 2); + place(0, 6, themeLabel, 2); + place(2, 6, mThemeDropDown, 2).setPadding(2); - place(0, 7, space, 1, 1); + place(0, 7, fontSizeLabel, 2); + place(2, 7, mFontSizeDropDown, 2).setPadding(2); - place(0, 8, mAlphaSlider, 2); - place(2, 8, alphaLabel, 2); + place(0, 8, space, 1, 1); - place(0, 9, mSpeechSlider, 2); - place(2, 9, speechLabel, 2); - place(4, 9, mSpeechLabel, 2).setPadding(2); -} + place(0, 9, mAlphaSlider, 2); + place(2, 9, alphaLabel, 2); -Setup_Interface::~Setup_Interface() -{ - delete mFontSizeListModel; + place(0, 10, mSpeechSlider, 2); + place(2, 10, speechLabel, 2); + place(4, 10, mSpeechLabel, 2).setPadding(2); } +Setup_Interface::~Setup_Interface() = default; + void Setup_Interface::apply() { - config.fontSize = mFontSizeDropDown->getSelected() + 10; + auto &theme = gui->getAvailableThemes().at(mThemeDropDown->getSelected()); + auto fontSize = mFontSizeDropDown->getSelected() + 10; + if (config.theme != theme.getPath() || config.fontSize != fontSize) + { + new OkDialog(_("Changing Theme or Font Size"), + _("Theme and font size changes will apply after restart.")); + } + config.theme = theme.getPath(); + config.fontSize = fontSize; mShowMonsterDamageEnabled = config.showMonstersTakedDamage; mVisibleNamesEnabled = config.visibleNames; @@ -201,6 +245,8 @@ void Setup_Interface::cancel() mSpeechSlider->setValue(mSpeechMode); mNameCheckBox->setSelected(mNameEnabled); mNPCLogCheckBox->setSelected(mNPCLogEnabled); + mThemeDropDown->setSelected(ThemesListModel::getThemeIndex(config.theme)); + mFontSizeDropDown->setSelected(config.fontSize - 10); mAlphaSlider->setValue(mOpacity); //mAlphaSlider->setEnabled(!mSDLTransparencyDisabled); diff --git a/src/gui/setup_interface.h b/src/gui/setup_interface.h index 9f5bbf1a..027526a2 100644 --- a/src/gui/setup_interface.h +++ b/src/gui/setup_interface.h @@ -29,8 +29,6 @@ #include <guichan/actionlistener.hpp> #include <guichan/keylistener.hpp> -class FontSizeChoiceListModel; - class Setup_Interface : public SetupTab, public gcn::ActionListener, public gcn::KeyListener { @@ -53,11 +51,8 @@ class Setup_Interface : public SetupTab, public gcn::ActionListener, double mOpacity; Being::Speech mSpeechMode; - FontSizeChoiceListModel *mFontSizeListModel; - - gcn::Label *speechLabel; - gcn::Label *alphaLabel; - gcn::Label *fontSizeLabel; + std::unique_ptr<gcn::ListModel> mThemesListModel; + std::unique_ptr<gcn::ListModel> mFontSizeListModel; gcn::CheckBox *mShowMonsterDamageCheckBox; gcn::CheckBox *mVisibleNamesCheckBox; @@ -72,7 +67,7 @@ class Setup_Interface : public SetupTab, public gcn::ActionListener, gcn::Label *mSpeechLabel; gcn::Slider *mAlphaSlider; - int mFontSize; + gcn::DropDown *mThemeDropDown; gcn::DropDown *mFontSizeDropDown; }; diff --git a/src/gui/setup_video.cpp b/src/gui/setup_video.cpp index b5322525..313badd8 100644 --- a/src/gui/setup_video.cpp +++ b/src/gui/setup_video.cpp @@ -25,13 +25,13 @@ #include "configuration.h" #include "game.h" #include "graphics.h" -#include "gui/widgets/dropdown.h" #include "localplayer.h" #include "particle.h" #include "gui/okdialog.h" #include "gui/widgets/checkbox.h" +#include "gui/widgets/dropdown.h" #include "gui/widgets/label.h" #include "gui/widgets/layout.h" #include "gui/widgets/slider.h" diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp index 095dc209..1dec34be 100644 --- a/src/gui/widgets/scrollarea.cpp +++ b/src/gui/widgets/scrollarea.cpp @@ -167,6 +167,18 @@ void ScrollArea::drawBackground(gcn::Graphics *graphics) // background is drawn as part of the frame instead } +static void drawButton(gcn::Graphics *graphics, + SkinType skinType, + bool pressed, + const gcn::Rectangle &dim) +{ + WidgetState state(dim); + if (pressed) + state.flags |= STATE_SELECTED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), skinType, state); +} + void ScrollArea::drawUpButton(gcn::Graphics *graphics) { if (!mShowButtons) @@ -241,18 +253,6 @@ void ScrollArea::drawHMarker(gcn::Graphics *graphics) gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollAreaHMarker, state); } -void ScrollArea::drawButton(gcn::Graphics *graphics, - SkinType skinType, - bool pressed, - const gcn::Rectangle &dim) -{ - WidgetState state(dim); - if (pressed) - state.flags |= STATE_SELECTED; - - gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), skinType, state); -} - /** * Code copied from gcn::ScrollArea::checkPolicies to make sure it takes the * frame size of the content into account. diff --git a/src/gui/widgets/scrollarea.h b/src/gui/widgets/scrollarea.h index 7ad0ed00..0ba10578 100644 --- a/src/gui/widgets/scrollarea.h +++ b/src/gui/widgets/scrollarea.h @@ -21,8 +21,6 @@ #pragma once -#include "resources/theme.h" - #include <guichan/widgets/scrollarea.hpp> /** @@ -127,11 +125,6 @@ class ScrollArea : public gcn::ScrollArea void drawVMarker(gcn::Graphics *graphics) override; void drawHMarker(gcn::Graphics *graphics) override; - static void drawButton(gcn::Graphics *graphics, - SkinType skinType, - bool pressed, - const gcn::Rectangle &dim); - void checkPolicies() override; /** diff --git a/src/gui/widgets/textfield.cpp b/src/gui/widgets/textfield.cpp index 9f35a5dd..6c866477 100644 --- a/src/gui/widgets/textfield.cpp +++ b/src/gui/widgets/textfield.cpp @@ -114,7 +114,7 @@ int TextField::getValue() const void TextField::drawCaret(gcn::Graphics *graphics, int x) { - graphics->setColor(getForegroundColor()); + graphics->setColor(Theme::getThemeColor(Theme::CARET)); graphics->drawLine(mPadding + x, mPadding, mPadding + x, getHeight() - mPadding); } diff --git a/src/log.cpp b/src/log.cpp index 9a5ebe8a..6f3253cf 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -45,14 +45,19 @@ void Logger::setLogFile(const std::string &logFilename) void Logger::log(const char *log_text, ...) { + va_list ap; + va_start(ap, log_text); + log(log_text, ap); + va_end(ap); +} + +void Logger::log(const char *log_text, va_list ap) +{ const size_t bufSize = 1024; char* buf = new char[bufSize]; - va_list ap; // Use a temporary buffer to fill in the variables - va_start(ap, log_text); vsnprintf(buf, bufSize, log_text, ap); - va_end(ap); // Get the current system time timeval tv; @@ -55,6 +55,8 @@ class Logger #endif ; + void log(const char *log_text, va_list ap); + /** * Log an error and quit. The error will be printed to standard error * and showm in a simple message box. diff --git a/src/resources/theme.cpp b/src/resources/theme.cpp index 9fb87dcd..6e57df0c 100644 --- a/src/resources/theme.cpp +++ b/src/resources/theme.cpp @@ -55,18 +55,33 @@ static void initDefaultThemePath() defaultThemePath = "graphics/gui/"; } -static std::optional<std::string> findThemePath(const std::string &theme) +static bool isThemePath(const std::string &theme) { - if (theme.empty()) - return {}; + return FS::exists(defaultThemePath + theme + "/theme.xml"); +} - std::string themePath = defaultThemePath; - themePath += theme; - if (FS::isDirectory(themePath)) - return themePath; +ThemeInfo::ThemeInfo(const std::string &path) + : path(path) +{ + auto themeFile = getFullPath() + "/theme.xml"; + if (!FS::exists(themeFile)) + return; - return {}; + auto doc = std::make_unique<XML::Document>(themeFile); + XML::Node rootNode = doc->rootNode(); + if (!rootNode || rootNode.name() != "theme") + return; + + if (rootNode.attribute("name", name) && !name.empty()) + this->doc = std::move(doc); + else + logger->log("Error: Theme '%s' has no name!", path.c_str()); +} + +std::string ThemeInfo::getFullPath() const +{ + return defaultThemePath + path; } @@ -212,13 +227,13 @@ void Skin::updateAlpha(float alpha) } -Theme::Theme(const std::string &path) +Theme::Theme(const ThemeInfo &themeInfo) : Palette(THEME_COLORS_END) - , mThemePath(path) + , mThemePath(themeInfo.getFullPath()) , mProgressColors(THEME_PROG_END) { listen(Event::ConfigChannel); - readTheme("theme.xml"); + readTheme(themeInfo); mColors[HIGHLIGHT].ch = 'H'; mColors[CHAT].ch = 'C'; @@ -244,13 +259,33 @@ std::string Theme::prepareThemePath() initDefaultThemePath(); // Try theme from settings - auto themePath = findThemePath(config.theme); + if (isThemePath(config.theme)) + return config.theme; // Try theme from branding - if (!themePath) - themePath = findThemePath(branding.getStringValue("theme")); + if (isThemePath(branding.getStringValue("theme"))) + return branding.getStringValue("theme"); - return themePath.value_or(defaultThemePath); + return std::string(); +} + +std::vector<ThemeInfo> Theme::getAvailableThemes() +{ + std::vector<ThemeInfo> themes; + themes.emplace_back(std::string()); + + for (const auto &entry : FS::enumerateFiles(defaultThemePath)) + { + ThemeInfo theme{entry}; + if (theme.isValid()) + themes.push_back(std::move(theme)); + } + + std::sort(themes.begin(), themes.end(), [](const ThemeInfo &a, const ThemeInfo &b) { + return a.getName() < b.getName(); + }); + + return themes; } std::string Theme::resolvePath(const std::string &path) const @@ -407,20 +442,21 @@ static bool check(bool value, const char *msg, ...) { if (!value) { - va_list args; - va_start(args, msg); - logger->log(msg, args); - va_end(args); + va_list ap; + va_start(ap, msg); + logger->log(msg, ap); + va_end(ap); } return !value; } -bool Theme::readTheme(const std::string &filename) +bool Theme::readTheme(const ThemeInfo &themeInfo) { - logger->log("Loading theme '%s'.", filename.c_str()); + logger->log("Loading %s theme from '%s'...", + themeInfo.getName().c_str(), + themeInfo.getPath().c_str()); - XML::Document doc(resolvePath(filename)); - XML::Node rootNode = doc.rootNode(); + XML::Node rootNode = themeInfo.getDocument().rootNode(); if (!rootNode || rootNode.name() != "theme") return false; @@ -505,6 +541,25 @@ void Theme::readSkinNode(XML::Node node) readSkinStateNode(childNode, skin); } +static void readSkinStateRectNode(XML::Node node, SkinState &state) +{ + auto &part = state.parts.emplace_back(); + auto &rect = part.data.emplace<ColoredRectangle>(); + + node.attribute("color", rect.color); + node.attribute("alpha", rect.color.a); + node.attribute("fill", rect.filled); +} + +static void readSkinStateTextNode(XML::Node node, SkinState &state) +{ + auto &textFormat = state.textFormat; + node.attribute("bold", textFormat.bold); + node.attribute("color", textFormat.color); + node.attribute("outlineColor", textFormat.outlineColor); + node.attribute("shadowColor", textFormat.shadowColor); +} + void Theme::readSkinStateNode(XML::Node node, Skin &skin) const { SkinState state; @@ -539,15 +594,6 @@ void Theme::readSkinStateNode(XML::Node node, Skin &skin) const skin.addState(std::move(state)); } -void Theme::readSkinStateTextNode(XML::Node node, SkinState &state) const -{ - auto &textFormat = state.textFormat; - node.attribute("bold", textFormat.bold); - node.attribute("color", textFormat.color); - node.attribute("outlineColor", textFormat.outlineColor); - node.attribute("shadowColor", textFormat.shadowColor); -} - template<> inline void fromString(const char *str, FillMode &value) { @@ -648,16 +694,6 @@ inline void fromString(const char *str, gcn::Color &value) value = gcn::Color(v); } -void Theme::readSkinStateRectNode(XML::Node node, SkinState &state) const -{ - auto &part = state.parts.emplace_back(); - auto &rect = part.data.emplace<ColoredRectangle>(); - - node.attribute("color", rect.color); - node.attribute("alpha", rect.color.a); - node.attribute("fill", rect.filled); -} - void Theme::readIconNode(XML::Node node) { std::string name; @@ -694,10 +730,11 @@ void Theme::readIconNode(XML::Node node) mIcons[name] = image->getSubImage(x, y, width, height); } -static int readColorType(const std::string &type) +static int readColorId(const std::string &id) { static constexpr const char *colors[Theme::THEME_COLORS_END] = { "TEXT", + "CARET", "SHADOW", "OUTLINE", "PARTY_CHAT_TAB", @@ -735,17 +772,17 @@ static int readColorType(const std::string &type) "SERVER_VERSION_NOT_SUPPORTED" }; - if (type.empty()) + if (id.empty()) return -1; for (int i = 0; i < Theme::THEME_COLORS_END; i++) - if (type == colors[i]) + if (id == colors[i]) return i; return -1; } -static Palette::GradientType readColorGradient(const std::string &grad) +static Palette::GradientType readGradientType(const std::string &grad) { static constexpr const char *grads[] = { "STATIC", @@ -766,20 +803,21 @@ static Palette::GradientType readColorGradient(const std::string &grad) void Theme::readColorNode(XML::Node node) { - const int type = readColorType(node.getProperty("id", std::string())); - if (check(type > 0, "Theme: 'color' element has invalid or no 'type' attribute!")) + const auto idStr = node.getProperty("id", std::string()); + const int id = readColorId(idStr); + if (check(id >= 0, "Theme: 'color' element has unknown 'id' attribute: '%s'!", idStr.c_str())) return; gcn::Color color; if (check(node.attribute("color", color), "Theme: 'color' element missing 'color' attribute!")) return; - const GradientType grad = readColorGradient(node.getProperty("effect", std::string())); + const GradientType grad = readGradientType(node.getProperty("effect", std::string())); - mColors[type].set(type, color, grad, 10); + mColors[id].set(id, color, grad, 10); } -static int readProgressType(const std::string &type) +static int readProgressId(const std::string &id) { static constexpr const char *colors[Theme::THEME_PROG_END] = { "DEFAULT", @@ -792,11 +830,11 @@ static int readProgressType(const std::string &type) "JOB" }; - if (type.empty()) + if (id.empty()) return -1; for (int i = 0; i < Theme::THEME_PROG_END; i++) - if (type == colors[i]) + if (id == colors[i]) return i; return -1; @@ -804,9 +842,10 @@ static int readProgressType(const std::string &type) void Theme::readProgressBarNode(XML::Node node) { - const int type = readProgressType(node.getProperty("id", std::string())); - if (type < 0) // invalid or no type given + const auto idStr = node.getProperty("id", std::string()); + const int id = readProgressId(idStr); + if (check(id >= 0, "Theme: 'progress' element has unknown 'id' attribute: '%s'!", idStr.c_str())) return; - mProgressColors[type] = std::make_unique<DyePalette>(node.getProperty("color", std::string())); + mProgressColors[id] = std::make_unique<DyePalette>(node.getProperty("color", std::string())); } diff --git a/src/resources/theme.h b/src/resources/theme.h index 18a0b54a..b83c8aa3 100644 --- a/src/resources/theme.h +++ b/src/resources/theme.h @@ -44,6 +44,25 @@ class Image; class ImageSet; class ProgressBar; +class ThemeInfo +{ +public: + ThemeInfo() = default; + explicit ThemeInfo(const std::string &path); + + bool isValid() const { return !name.empty(); } + + const std::string &getName() const { return name; } + const std::string &getPath() const { return path; } + std::string getFullPath() const; + XML::Document &getDocument() const { return *doc; } + +private: + std::string name; + std::string path; + std::unique_ptr<XML::Document> doc; +}; + enum class SkinType { Window, @@ -172,10 +191,13 @@ class Theme : public Palette, public EventListener { public: static std::string prepareThemePath(); + static std::vector<ThemeInfo> getAvailableThemes(); - Theme(const std::string &path); + Theme(const ThemeInfo &themeInfo); ~Theme() override; + const std::string &getThemePath() const { return mThemePath; } + /** * Returns the patch to the given GUI resource relative to the theme * or, if it isn't in the theme, relative to 'graphics/gui'. @@ -185,6 +207,7 @@ class Theme : public Palette, public EventListener enum ThemePalette { TEXT, + CARET, SHADOW, OUTLINE, PARTY_CHAT_TAB, @@ -286,12 +309,10 @@ class Theme : public Palette, public EventListener ResourceRef<Image> getImage(const std::string &path) const; - bool readTheme(const std::string &filename); + bool readTheme(const ThemeInfo &themeInfo); void readSkinNode(XML::Node node); void readSkinStateNode(XML::Node node, Skin &skin) const; - void readSkinStateTextNode(XML::Node node, SkinState &state) const; void readSkinStateImgNode(XML::Node node, SkinState &state) const; - void readSkinStateRectNode(XML::Node node, SkinState &state) const; void readIconNode(XML::Node node); void readColorNode(XML::Node node); void readProgressBarNode(XML::Node node); diff --git a/src/utils/xml.h b/src/utils/xml.h index 787504bb..d268c52c 100644 --- a/src/utils/xml.h +++ b/src/utils/xml.h @@ -66,10 +66,19 @@ namespace XML class Iterator { public: - explicit Iterator(xmlNodePtr node) : mNode(node) {} + explicit Iterator(xmlNodePtr node) : mNode(node) + { + while (mNode && mNode->type != XML_ELEMENT_NODE) + mNode = mNode->next; + } bool operator!=(const Iterator &other) const { return mNode != other.mNode; } - void operator++() { mNode = mNode->next; } + void operator++() + { + do { + mNode = mNode->next; + } while (mNode && mNode->type != XML_ELEMENT_NODE); + } Node operator*() const { return mNode; } private: |