36#include <guichan/font.hpp>
37#include <guichan/widget.hpp>
46static std::string defaultThemePath;
48static void initDefaultThemePath()
53 defaultThemePath =
"graphics/gui/";
56static bool isThemePath(
const std::string &theme)
58 return FS::exists(defaultThemePath + theme +
"/theme.xml");
69 auto doc = std::make_unique<XML::Document>(themeFile);
71 if (!rootNode || rootNode.
name() !=
"theme")
75 this->
doc = std::move(
doc);
82 return defaultThemePath +
path;
87 : width(widget->getWidth())
88 , height(widget->getHeight())
93 if (!widget->isEnabled())
95 if (widget->isFocused())
112 for (
auto &part : state.parts)
113 if (
auto image = std::get_if<Image *>(&part.data))
119 mStates.emplace_back(std::move(state));
129 for (
const auto &part : skinState->parts)
131 std::visit([&](
const auto &data) {
132 using T = std::decay_t<
decltype(data)>;
134 if constexpr (std::is_same_v<T, ImageRect>)
137 state.
x + part.offsetX,
138 state.
y + part.offsetY,
142 else if constexpr (std::is_same_v<T, Image*>)
146 else if constexpr (std::is_same_v<T, ColoredRectangle>)
152 const gcn::Rectangle rect(state.
x + part.offsetX,
153 state.
y + part.offsetY,
170 for (
const auto &skinState :
mStates)
171 if (skinState.stateFlags == (skinState.setFlags & flags))
181 for (
const auto &state :
mStates)
183 for (
const auto &part : state.parts)
185 if (
auto imageRect = std::get_if<ImageRect>(&part.data))
186 minWidth = std::max(minWidth, imageRect->minWidth());
187 else if (
auto img = std::get_if<Image *>(&part.data))
188 minWidth = std::max(minWidth, (*img)->getWidth());
199 for (
const auto &state :
mStates)
201 for (
const auto &part : state.parts)
203 if (
auto imageRect = std::get_if<ImageRect>(&part.data))
204 minHeight = std::max(minHeight, imageRect->minHeight());
205 else if (
auto img = std::get_if<Image *>(&part.data))
206 minHeight = std::max(minHeight, (*img)->getHeight());
217 for (
auto &part : state.parts)
219 if (
auto rect = std::get_if<ImageRect>(&part.data))
220 rect->image->setAlpha(alpha);
221 else if (
auto img = std::get_if<Image *>(&part.data))
222 (*img)->setAlpha(alpha);
229 : mThemePath(themeInfo.getFullPath())
236 Log::info(
"Error, theme did not define any palettes: %s",
246 for (
auto &[
_, image] :
mIcons)
252 initDefaultThemePath();
262 return std::string();
267 std::vector<ThemeInfo> themes;
268 themes.emplace_back(std::string());
274 themes.push_back(std::move(theme));
278 return a.getName() < b.getName();
287 int pos = path.find(
'|');
290 file = path.substr(0, pos);
300 return defaultThemePath +
"/" + path;
320 int color[3] = {0, 0, 0};
323 dye->getColor(progress, color);
325 return gcn::Color(color[0], color[1], color[2]);
341 case '0':
return BLACK;
342 case '1':
return RED;
343 case '2':
return GREEN;
344 case '3':
return BLUE;
347 case '6':
return PINK;
349 case '8':
return GRAY;
350 case '9':
return BROWN;
353 case 'C':
return CHAT;
360 case 'P':
return PARTY;
361 case 'U':
return GUILD;
379 const gcn::Rectangle &area,
380 const gcn::Color &color,
382 const std::string &text,
389 widgetState.
x = area.x;
390 widgetState.
y = area.y;
391 widgetState.
width = area.width;
392 widgetState.
height = area.height;
401 graphics->fillRectangle(gcn::Rectangle(area.x + 4,
403 (
int) (progress * (area.width - 8)),
410 if (
auto skinState = skin.getState(widgetState.
flags))
412 const TextFormat *textFormat = &skinState->textFormat;
418 const int textX = area.x + area.width / 2;
419 const int textY = area.y + (area.height - font->getHeight()) / 2;
424 gcn::Graphics::CENTER,
436 static Skin emptySkin;
437 const auto it =
mSkins.find(skinType);
438 return it !=
mSkins.end() ? it->second : emptySkin;
443 auto it =
mIcons.find(name);
452 if (minimumOpacity > 1.0f)
481static bool check(
bool value,
const char *msg, ...)
495 Log::info(
"Loading %s theme from '%s'...",
501 if (!rootNode || rootNode.
name() !=
"theme")
504 for (
auto childNode : rootNode.
children())
506 if (childNode.name() ==
"skin")
508 else if (childNode.name() ==
"palette")
510 else if (childNode.name() ==
"progressbar")
512 else if (childNode.name() ==
"icon")
515 Log::info(
"Theme: Unknown node '%s'!", childNode.name().data());
526static std::optional<SkinType> readSkinType(std::string_view type)
564 const auto skinTypeStr = node.
getProperty(
"type", std::string());
565 const auto skinType = readSkinType(skinTypeStr);
566 if (check(skinType.has_value(),
"Theme: Unknown skin type '%s'", skinTypeStr.c_str()))
569 auto &skin =
mSkins[*skinType];
573 node.
attribute(
"frameSize", skin.frameSize);
576 node.
attribute(
"titleBarHeight", skin.titleBarHeight);
577 node.
attribute(
"titleOffsetX", skin.titleOffsetX);
578 node.
attribute(
"titleOffsetY", skin.titleOffsetY);
580 node.
attribute(
"showButtons", skin.showButtons);
582 for (
auto childNode : node.
children())
583 if (childNode.name() ==
"state")
589 auto &part = state.
parts.emplace_back();
609 auto readFlag = [&] (
const char *name,
int flag)
611 std::optional<bool> value;
614 if (value.has_value())
626 for (
auto childNode : node.
children())
628 if (childNode.name() ==
"img")
630 else if (childNode.name() ==
"rect")
631 readSkinStateRectNode(childNode, state);
632 else if (childNode.name() ==
"text")
642 if (strcmp(str,
"repeat") == 0)
644 else if (strcmp(str,
"stretch") == 0)
650 const std::string src = node.
getProperty(
"src", std::string());
651 if (check(!src.empty(),
"Theme: 'img' element has empty 'src' attribute!"))
655 if (check(image,
"Theme: Failed to load image '%s'!", src.c_str()))
664 int width = image->getWidth();
665 int height = image->getHeight();
676 if (check(left >= 0 || right >= 0 || top >= 0 || bottom >= 0,
"Theme: Invalid border value!"))
678 if (check(x >= 0 || y >= 0,
"Theme: Invalid position value!"))
680 if (check(width >= 0 || height >= 0,
"Theme: Invalid size value!"))
682 if (check(x + width <= image->getWidth() || y + height <= image->getHeight(),
"Theme: Image size out of bounds!"))
685 auto &part = state.
parts.emplace_back();
690 if (left + right + top + bottom > 0)
692 auto &border = part.data.emplace<
ImageRect>();
694 border.right = right;
696 border.bottom = bottom;
697 border.image.reset(image->getSubImage(x, y, width, height));
703 part.data = image->getSubImage(x, y, width, height);
710 if (strlen(str) < 7 || str[0] !=
'#')
713 Log::info(
"Error, invalid theme color palette: %s", str);
714 value = gcn::Color(0, 0, 0);
719 for (
int i = 1; i < 7; ++i)
724 if (
'0' <= c && c <=
'9')
726 else if (
'A' <= c && c <=
'F')
728 else if (
'a' <= c && c <=
'f')
736 value = gcn::Color(v);
746 if (check(!name.empty(),
"Theme: 'icon' element has empty 'name' attribute!"))
748 if (check(!src.empty(),
"Theme: 'icon' element has empty 'src' attribute!"))
752 if (check(image,
"Theme: Failed to load image '%s'!", src.c_str()))
757 int width = image->getWidth();
758 int height = image->getHeight();
765 if (check(x >= 0 || y >= 0,
"Theme: Invalid position value!"))
767 if (check(width >= 0 || height >= 0,
"Theme: Invalid size value!"))
769 if (check(x + width <= image->getWidth() || y + height <= image->getHeight(),
"Theme: Image size out of bounds!"))
772 mIcons[name] = image->getSubImage(x, y, width, height);
775static int readColorId(
const std::string &
id)
828 "SERVER_VERSION_NOT_SUPPORTED"
843 static constexpr const char *grads[] = {
853 for (
int i = 0; i < 4; i++)
854 if (grad == grads[i])
862 const auto idStr = node.
getProperty(
"id", std::string());
863 const int id = readColorId(idStr);
864 if (check(
id >= 0,
"Theme: 'color' element has unknown 'id' attribute: '%s'!", idStr.c_str()))
868 if (check(node.
attribute(
"color", color),
"Theme: 'color' element missing 'color' attribute!"))
871 std::optional<gcn::Color> outlineColor;
872 node.
attribute(
"outlineColor", outlineColor);
874 const auto grad = readGradientType(node.
getProperty(
"effect", std::string()));
875 palette.
setColor(
id, color, outlineColor, grad, 10);
881 if (node.
attribute(
"id", paletteId) &&
static_cast<size_t>(paletteId) !=
mPalettes.size())
882 Log::info(
"Theme: Non-consecutive palette 'id' attribute with value %d!", paletteId);
886 for (
auto childNode : node.
children())
888 if (childNode.name() ==
"color")
889 readColorNode(childNode, palette);
891 Log::info(
"Theme: Unknown node '%s'!", childNode.name().data());
895static int readProgressId(
const std::string &
id)
920 const auto idStr = node.
getProperty(
"id", std::string());
921 const int id = readProgressId(idStr);
922 if (check(
id >= 0,
"Theme: 'progress' element has unknown 'id' attribute: '%s'!", idStr.c_str()))
929 for (
auto childNode : node.
children())
931 if (childNode.name() ==
"text")
934 Log::info(
"Theme: Unknown node '%s' in progressbar!", childNode.name().data());
std::string getStringValue(const std::string &key) const
void listen(Event::Channel channel)
A central point of control for graphics.
gcn::Font * getFont() const
void drawImageRect(const ImageRect &imgRect, int x, int y, int w, int h)
Draws a rectangle using images.
void drawText(const std::string &text, int x, int y, gcn::Graphics::Alignment alignment, const gcn::Color &color, gcn::Font *font, bool outline=false, bool shadow=false, const std::optional< gcn::Color > &outlineColor={}, const std::optional< gcn::Color > &shadowColor={})
void setColor(const gcn::Color &color) override
const gcn::Color & getColor() const final
bool drawImage(const Image *image, int x, int y)
Blits an image onto the screen.
gcn::Font * getFont() const
Return game font.
Theme * getTheme() const
The global GUI theme.
Defines a class for loading and storing images.
Class controlling the game's color palette.
const gcn::Color & getColor(int type) const
Gets the color associated with the type.
void setColor(int type, const gcn::Color &color, const std::optional< gcn::Color > &outlineColor, GradientType grad, int delay)
GradientType
Colors can be static or can alter over time.
static ResourceManager * getInstance()
Returns an instance of the class, creating one if it does not already exist.
ResourceRef< Image > getImage(const std::string &idPath)
Loads the Image resource found at the given identifier path.
Automatically counting Resource reference.
void updateAlpha(float alpha)
Updates the alpha value of the skin.
void draw(Graphics *graphics, const WidgetState &state) const
void addState(SkinState state)
int getMinWidth() const
Returns the minimum width which can be used with this skin.
std::vector< SkinState > mStates
const SkinState * getState(uint8_t flags) const
int getMinHeight() const
Returns the minimum height which can be used with this skin.
const std::string & getPath() const
const std::string & getName() const
std::unique_ptr< XML::Document > doc
const XML::Document & getDocument() const
std::string getFullPath() const
void readSkinStateNode(XML::Node node, Skin &skin) const
Theme(const ThemeInfo &themeInfo)
static std::string prepareThemePath()
bool readTheme(const ThemeInfo &themeInfo)
float mMinimumOpacity
Tells if the current skins opacity should not get less than the given value.
std::string resolvePath(const std::string &path) const
Returns the patch to the given GUI resource relative to the theme or, if it isn't in the theme,...
const Skin & getSkin(SkinType skinType) const
static const gcn::Color & getThemeColor(int type)
Gets the color associated with the type in the default palette (0).
std::array< std::unique_ptr< DyePalette >, THEME_PROG_END > mProgressColors
ResourceRef< Image > getImage(const std::string &path) const
std::vector< Palette > mPalettes
const Palette & getPalette(size_t index) const
void updateAlpha()
Updates the alpha values of all of the skins and images.
static ResourceRef< Image > getImageFromTheme(const std::string &path)
void readProgressBarNode(XML::Node node)
static std::optional< int > getColorIdForChar(char c)
Returns the color ID associated with a character, if it exists.
void readSkinNode(XML::Node node)
void readIconNode(XML::Node node)
void readSkinStateImgNode(XML::Node node, SkinState &state) const
void setMinimumOpacity(float minimumOpacity)
Set the minimum opacity allowed to skins.
void drawProgressBar(Graphics *graphics, const gcn::Rectangle &area, const gcn::Color &color, float progress, const std::string &text=std::string(), ProgressPalette progressType=ProgressPalette::THEME_PROG_END) const
static std::vector< ThemeInfo > getAvailableThemes()
const gcn::Color & getColor(int type) const
Returns a color from the default palette (0).
static gcn::Color getProgressColor(int type, float progress)
const Image * getIcon(const std::string &name) const
void event(Event::Channel channel, const Event &event) override
void readPaletteNode(XML::Node node)
std::map< std::string, Image * > mIcons
void drawSkin(Graphics *graphics, SkinType type, const WidgetState &state) const
std::array< std::optional< TextFormat >, THEME_PROG_END > mProgressTextFormats
std::map< SkinType, Skin > mSkins
Node rootNode() const
Returns the root node of the document (or NULL if there was a load error).
std::string_view name() const
int getProperty(const char *name, int def) const
Children children() const
bool attribute(const char *name, T &value) const
Config config
Global settings (config.xml)
Configuration branding
XML branding information reader.
gcn::Font * boldFont
Bolded text font.
bool isDirectory(const std::string &path)
Checks whether the given path is a directory.
bool exists(const std::string &path)
Checks whether the given file or directory exists in the search path.
Files enumerateFiles(const std::string &dir)
Returns a list of files in the given directory.
void info(const char *log_text,...) LOG_PRINTF_ATTR
void error(const char *log_text,...) LOG_PRINTF_ATTR
void vinfo(const char *log_text, va_list ap)
An image reference along with the margins specifying how to render this image at different sizes.
std::vector< SkinPart > parts
std::optional< gcn::Color > shadowColor
std::optional< gcn::Color > outlineColor
void fromString(const char *str, FillMode &value)