summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-04-11 12:17:56 +0200
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-04-25 14:05:30 +0000
commit243b6c163cd91b874265d5acee57e754dec2e979 (patch)
treeb64c57272e408eabe375957bb4d4b05df5aa48fc
parentca29443d65152fde5ad960f5c02b5fc0f2dd378f (diff)
downloadmana-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.xml4
-rw-r--r--data/graphics/gui/theme.xml1
-rw-r--r--src/configuration.h2
-rw-r--r--src/gui/gui.cpp20
-rw-r--r--src/gui/gui.h12
-rw-r--r--src/gui/setup_interface.cpp92
-rw-r--r--src/gui/setup_interface.h11
-rw-r--r--src/gui/setup_video.cpp2
-rw-r--r--src/gui/widgets/scrollarea.cpp24
-rw-r--r--src/gui/widgets/scrollarea.h7
-rw-r--r--src/gui/widgets/textfield.cpp2
-rw-r--r--src/log.cpp11
-rw-r--r--src/log.h2
-rw-r--r--src/resources/theme.cpp151
-rw-r--r--src/resources/theme.h29
-rw-r--r--src/utils/xml.h13
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;
diff --git a/src/log.h b/src/log.h
index 81e6bc95..593fda02 100644
--- a/src/log.h
+++ b/src/log.h
@@ -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: