Mana
Loading...
Searching...
No Matches
theme.cpp
Go to the documentation of this file.
1/*
2 * Gui Skinning
3 * Copyright (C) 2008 The Legend of Mazzeroth Development Team
4 * Copyright (C) 2009 Aethyra Development Team
5 * Copyright (C) 2009 The Mana World Development Team
6 * Copyright (C) 2009-2012 The Mana Developers
7 *
8 * This file is part of The Mana Client.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24#include "resources/theme.h"
25
26#include "configuration.h"
27#include "log.h"
28
29#include "resources/dye.h"
30#include "resources/image.h"
31#include "resources/imageset.h"
33
34#include "utils/filesystem.h"
35
36#include <guichan/font.hpp>
37#include <guichan/widget.hpp>
38
39#include <algorithm>
40
46static std::string defaultThemePath;
47
48static void initDefaultThemePath()
49{
50 defaultThemePath = branding.getStringValue("guiThemePath");
51
52 if (defaultThemePath.empty() || !FS::isDirectory(defaultThemePath))
53 defaultThemePath = "graphics/gui/";
54}
55
56static bool isThemePath(const std::string &theme)
57{
58 return FS::exists(defaultThemePath + theme + "/theme.xml");
59}
60
61
62ThemeInfo::ThemeInfo(const std::string &path)
63 : path(path)
64{
65 auto themeFile = getFullPath() + "/theme.xml";
66 if (!FS::exists(themeFile))
67 return;
68
69 auto doc = std::make_unique<XML::Document>(themeFile);
70 XML::Node rootNode = doc->rootNode();
71 if (!rootNode || rootNode.name() != "theme")
72 return;
73
74 if (rootNode.attribute("name", name) && !name.empty())
75 this->doc = std::move(doc);
76 else
77 Log::error("Theme '%s' has no name!", path.c_str());
78}
79
80std::string ThemeInfo::getFullPath() const
81{
82 return defaultThemePath + path;
83}
84
85
86WidgetState::WidgetState(const gcn::Widget *widget)
87 : width(widget->getWidth())
88 , height(widget->getHeight())
89{
90 // x and y are not set based on the widget because the rendering usually
91 // happens in local coordinates.
92
93 if (!widget->isEnabled())
95 if (widget->isFocused())
97}
98
99WidgetState::WidgetState(const gcn::Rectangle &dim, uint8_t flags)
100 : x(dim.x)
101 , y(dim.y)
102 , width(dim.width)
103 , height(dim.height)
104 , flags(flags)
105{}
106
107
109{
110 // Raw Image* need explicit deletion
111 for (auto &state : mStates)
112 for (auto &part : state.parts)
113 if (auto image = std::get_if<Image *>(&part.data))
114 delete *image;
115}
116
118{
119 mStates.emplace_back(std::move(state));
120}
121
122void Skin::draw(Graphics *graphics, const WidgetState &state) const
123{
124 // Only draw the first matching state
125 auto skinState = getState(state.flags);
126 if (!skinState)
127 return;
128
129 for (const auto &part : skinState->parts)
130 {
131 std::visit([&](const auto &data) {
132 using T = std::decay_t<decltype(data)>;
133
134 if constexpr (std::is_same_v<T, ImageRect>)
135 {
137 state.x + part.offsetX,
138 state.y + part.offsetY,
139 state.width,
140 state.height);
141 }
142 else if constexpr (std::is_same_v<T, Image*>)
143 {
144 graphics->drawImage(data, state.x + part.offsetX, state.y + part.offsetY);
145 }
146 else if constexpr (std::is_same_v<T, ColoredRectangle>)
147 {
148 const auto color = graphics->getColor();
149 // TODO: Take GUI alpha into account
150 graphics->setColor(data.color);
151
152 const gcn::Rectangle rect(state.x + part.offsetX,
153 state.y + part.offsetY,
154 state.width,
155 state.height);
156
157 if (data.filled)
158 graphics->fillRectangle(rect);
159 else
160 graphics->drawRectangle(rect);
161
162 graphics->setColor(color);
163 }
164 }, part.data);
165 }
166}
167
169{
170 for (const auto &skinState : mStates)
171 if (skinState.stateFlags == (skinState.setFlags & flags))
172 return &skinState;
173
174 return nullptr;
175}
176
178{
179 int minWidth = 0;
180
181 for (const auto &state : mStates)
182 {
183 for (const auto &part : state.parts)
184 {
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());
189 }
190 }
191
192 return minWidth;
193}
194
196{
197 int minHeight = 0;
198
199 for (const auto &state : mStates)
200 {
201 for (const auto &part : state.parts)
202 {
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());
207 }
208 }
209
210 return minHeight;
211}
212
213void Skin::updateAlpha(float alpha)
214{
215 for (auto &state : mStates)
216 {
217 for (auto &part : state.parts)
218 {
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);
223 }
224 }
225}
226
227
228Theme::Theme(const ThemeInfo &themeInfo)
229 : mThemePath(themeInfo.getFullPath())
230{
232 readTheme(themeInfo);
233
234 if (mPalettes.empty())
235 {
236 Log::info("Error, theme did not define any palettes: %s",
237 themeInfo.getPath().c_str());
238
239 // Avoid crashing
240 mPalettes.emplace_back(THEME_COLORS_END);
241 }
242}
243
245{
246 for (auto &[_, image] : mIcons)
247 delete image;
248}
249
251{
252 initDefaultThemePath();
253
254 // Try theme from settings
255 if (isThemePath(config.theme))
256 return config.theme;
257
258 // Try theme from branding
259 if (isThemePath(branding.getStringValue("theme")))
260 return branding.getStringValue("theme");
261
262 return std::string();
263}
264
265std::vector<ThemeInfo> Theme::getAvailableThemes()
266{
267 std::vector<ThemeInfo> themes;
268 themes.emplace_back(std::string());
269
270 for (const auto &entry : FS::enumerateFiles(defaultThemePath))
271 {
272 ThemeInfo theme{entry};
273 if (theme.isValid())
274 themes.push_back(std::move(theme));
275 }
276
277 std::sort(themes.begin(), themes.end(), [](const ThemeInfo &a, const ThemeInfo &b) {
278 return a.getName() < b.getName();
279 });
280
281 return themes;
282}
283
284std::string Theme::resolvePath(const std::string &path) const
285{
286 // Need to strip off any dye info for the existence tests
287 int pos = path.find('|');
288 std::string file;
289 if (pos > 0)
290 file = path.substr(0, pos);
291 else
292 file = path;
293
294 // Try the theme
295 file = mThemePath + "/" + file;
296 if (FS::exists(file))
297 return mThemePath + "/" + path;
298
299 // Backup
300 return defaultThemePath + "/" + path;
301}
302
303ResourceRef<Image> Theme::getImage(const std::string &path) const
304{
306}
307
309{
310 return gui->getTheme()->getImage(path);
311}
312
313const gcn::Color &Theme::getThemeColor(int type)
314{
315 return gui->getTheme()->getColor(type);
316}
317
318gcn::Color Theme::getProgressColor(int type, float progress)
319{
320 int color[3] = {0, 0, 0};
321
322 if (const auto &dye = gui->getTheme()->mProgressColors[type])
323 dye->getColor(progress, color);
324
325 return gcn::Color(color[0], color[1], color[2]);
326}
327
328const Palette &Theme::getPalette(size_t index) const
329{
330 return mPalettes.at(index < mPalettes.size() ? index : 0);
331}
332
333const gcn::Color &Theme::getColor(int type) const
334{
335 return getPalette(0).getColor(type);
336}
337
338std::optional<int> Theme::getColorIdForChar(char c)
339{
340 switch (c) {
341 case '0': return BLACK;
342 case '1': return RED;
343 case '2': return GREEN;
344 case '3': return BLUE;
345 case '4': return ORANGE;
346 case '5': return YELLOW;
347 case '6': return PINK;
348 case '7': return PURPLE;
349 case '8': return GRAY;
350 case '9': return BROWN;
351
352 case 'H': return HIGHLIGHT;
353 case 'C': return CHAT;
354 case 'G': return GM;
355 case 'g': return GLOBAL;
356 case 'Y': return PLAYER;
357 case 'W': return WHISPER;
358 // case 'w': return WHISPER_TAB_OFFLINE;
359 case 'I': return IS;
360 case 'P': return PARTY;
361 case 'U': return GUILD;
362 case 'S': return SERVER;
363 case 'L': return LOGGER;
364 case '<': return HYPERLINK;
365 // case 's': return SELFNICK;
366 case 'o': return OLDCHAT;
367 case 'a': return AWAYCHAT;
368 }
369
370 return {};
371}
372
373void Theme::drawSkin(Graphics *graphics, SkinType type, const WidgetState &state) const
374{
375 getSkin(type).draw(graphics, state);
376}
377
379 const gcn::Rectangle &area,
380 const gcn::Color &color,
381 float progress,
382 const std::string &text,
383 ProgressPalette progressType) const
384{
385 gcn::Font *oldFont = graphics->getFont();
386 gcn::Color oldColor = graphics->getColor();
387
388 WidgetState widgetState;
389 widgetState.x = area.x;
390 widgetState.y = area.y;
391 widgetState.width = area.width;
392 widgetState.height = area.height;
393
394 auto &skin = getSkin(SkinType::ProgressBar);
395 skin.draw(graphics, widgetState);
396
397 // The bar
398 if (progress > 0)
399 {
400 graphics->setColor(color);
401 graphics->fillRectangle(gcn::Rectangle(area.x + 4,
402 area.y + 4,
403 (int) (progress * (area.width - 8)),
404 area.height - 8));
405 }
406
407 // The label
408 if (!text.empty())
409 {
410 if (auto skinState = skin.getState(widgetState.flags))
411 {
412 const TextFormat *textFormat = &skinState->textFormat;
413
414 if (progressType < THEME_PROG_END && mProgressTextFormats[progressType])
415 textFormat = &(*mProgressTextFormats[progressType]);
416
417 auto font = textFormat->bold ? boldFont : gui->getFont();
418 const int textX = area.x + area.width / 2;
419 const int textY = area.y + (area.height - font->getHeight()) / 2;
420
421 graphics->drawText(text,
422 textX,
423 textY,
424 gcn::Graphics::CENTER,
425 font,
426 *textFormat);
427 }
428 }
429
430 graphics->setFont(oldFont);
431 graphics->setColor(oldColor);
432}
433
434const Skin &Theme::getSkin(SkinType skinType) const
435{
436 static Skin emptySkin;
437 const auto it = mSkins.find(skinType);
438 return it != mSkins.end() ? it->second : emptySkin;
439}
440
441const Image *Theme::getIcon(const std::string &name) const
442{
443 auto it = mIcons.find(name);
444 if (it == mIcons.end())
445 return nullptr;
446
447 return it->second;
448}
449
450void Theme::setMinimumOpacity(float minimumOpacity)
451{
452 if (minimumOpacity > 1.0f)
453 return;
454
455 mMinimumOpacity = minimumOpacity;
456 updateAlpha();
457}
458
460{
461 const float alpha = std::max(config.guiAlpha, mMinimumOpacity);
462 if (mAlpha == alpha)
463 return;
464
465 mAlpha = alpha;
466
467 for (auto &[_, skin] : mSkins)
468 skin.updateAlpha(mAlpha);
469}
470
471void Theme::event(Event::Channel channel, const Event &event)
472{
473 if (channel == Event::ConfigChannel &&
474 event.getType() == Event::ConfigOptionChanged &&
475 event.hasValue(&Config::guiAlpha))
476 {
477 updateAlpha();
478 }
479}
480
481static bool check(bool value, const char *msg, ...)
482{
483 if (!value)
484 {
485 va_list ap;
486 va_start(ap, msg);
487 Log::vinfo(msg, ap);
488 va_end(ap);
489 }
490 return !value;
491}
492
493bool Theme::readTheme(const ThemeInfo &themeInfo)
494{
495 Log::info("Loading %s theme from '%s'...",
496 themeInfo.getName().c_str(),
497 themeInfo.getPath().c_str());
498
499 XML::Node rootNode = themeInfo.getDocument().rootNode();
500
501 if (!rootNode || rootNode.name() != "theme")
502 return false;
503
504 for (auto childNode : rootNode.children())
505 {
506 if (childNode.name() == "skin")
507 readSkinNode(childNode);
508 else if (childNode.name() == "palette")
509 readPaletteNode(childNode);
510 else if (childNode.name() == "progressbar")
511 readProgressBarNode(childNode);
512 else if (childNode.name() == "icon")
513 readIconNode(childNode);
514 else
515 Log::info("Theme: Unknown node '%s'!", childNode.name().data());
516 }
517
518 Log::info("Finished loading theme.");
519
520 for (auto &[_, skin] : mSkins)
521 skin.updateAlpha(mAlpha);
522
523 return true;
524}
525
526static std::optional<SkinType> readSkinType(std::string_view type)
527{
528 if (type == "Window") return SkinType::Window;
529 if (type == "ToolWindow") return SkinType::ToolWindow;
530 if (type == "Popup") return SkinType::Popup;
531 if (type == "SpeechBubble") return SkinType::SpeechBubble;
532 if (type == "Desktop") return SkinType::Desktop;
533 if (type == "Button") return SkinType::Button;
534 if (type == "ButtonUp") return SkinType::ButtonUp;
535 if (type == "ButtonDown") return SkinType::ButtonDown;
536 if (type == "ButtonLeft") return SkinType::ButtonLeft;
537 if (type == "ButtonRight") return SkinType::ButtonRight;
538 if (type == "ButtonClose") return SkinType::ButtonClose;
539 if (type == "ButtonSticky") return SkinType::ButtonSticky;
540 if (type == "CheckBox") return SkinType::CheckBox;
541 if (type == "RadioButton") return SkinType::RadioButton;
542 if (type == "TextField") return SkinType::TextField;
543 if (type == "Tab") return SkinType::Tab;
544 if (type == "ScrollArea") return SkinType::ScrollArea;
545 if (type == "ScrollAreaHBar") return SkinType::ScrollAreaHBar;
546 if (type == "ScrollAreaHMarker") return SkinType::ScrollAreaHMarker;
547 if (type == "ScrollAreaVBar") return SkinType::ScrollAreaVBar;
548 if (type == "ScrollAreaVMarker") return SkinType::ScrollAreaVMarker;
549 if (type == "DropDownFrame") return SkinType::DropDownFrame;
550 if (type == "DropDownButton") return SkinType::DropDownButton;
551 if (type == "ProgressBar") return SkinType::ProgressBar;
552 if (type == "Slider") return SkinType::Slider;
553 if (type == "SliderHandle") return SkinType::SliderHandle;
554 if (type == "ResizeGrip") return SkinType::ResizeGrip;
555 if (type == "ShortcutBox") return SkinType::ShortcutBox;
556 if (type == "EquipmentBox") return SkinType::EquipmentBox;
557 if (type == "ItemSlot") return SkinType::ItemSlot;
558 if (type == "EmoteSlot") return SkinType::EmoteSlot;
559 return {};
560}
561
563{
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()))
567 return;
568
569 auto &skin = mSkins[*skinType];
570
571 node.attribute("width", skin.width);
572 node.attribute("height", skin.height);
573 node.attribute("frameSize", skin.frameSize);
574 node.attribute("padding", skin.padding);
575 node.attribute("spacing", skin.spacing);
576 node.attribute("titleBarHeight", skin.titleBarHeight);
577 node.attribute("titleOffsetX", skin.titleOffsetX);
578 node.attribute("titleOffsetY", skin.titleOffsetY);
579 node.attribute("palette", skin.palette);
580 node.attribute("showButtons", skin.showButtons);
581
582 for (auto childNode : node.children())
583 if (childNode.name() == "state")
584 readSkinStateNode(childNode, skin);
585}
586
587static void readSkinStateRectNode(XML::Node node, SkinState &state)
588{
589 auto &part = state.parts.emplace_back();
590 auto &rect = part.data.emplace<ColoredRectangle>();
591
592 node.attribute("color", rect.color);
593 node.attribute("alpha", rect.color.a);
594 node.attribute("fill", rect.filled);
595}
596
597static void readTextNode(XML::Node node, TextFormat &textFormat)
598{
599 node.attribute("bold", textFormat.bold);
600 node.attribute("color", textFormat.color);
601 node.attribute("outlineColor", textFormat.outlineColor);
602 node.attribute("shadowColor", textFormat.shadowColor);
603}
604
606{
607 SkinState state;
608
609 auto readFlag = [&] (const char *name, int flag)
610 {
611 std::optional<bool> value;
612 node.attribute(name, value);
613
614 if (value.has_value())
615 {
616 state.setFlags |= flag;
617 state.stateFlags |= *value ? flag : 0;
618 }
619 };
620
621 readFlag("selected", STATE_SELECTED);
622 readFlag("disabled", STATE_DISABLED);
623 readFlag("hovered", STATE_HOVERED);
624 readFlag("focused", STATE_FOCUSED);
625
626 for (auto childNode : node.children())
627 {
628 if (childNode.name() == "img")
629 readSkinStateImgNode(childNode, state);
630 else if (childNode.name() == "rect")
631 readSkinStateRectNode(childNode, state);
632 else if (childNode.name() == "text")
633 readTextNode(childNode, state.textFormat);
634 }
635
636 skin.addState(std::move(state));
637}
638
639template<>
640inline void fromString(const char *str, FillMode &value)
641{
642 if (strcmp(str, "repeat") == 0)
643 value = FillMode::Repeat;
644 else if (strcmp(str, "stretch") == 0)
645 value = FillMode::Stretch;
646}
647
649{
650 const std::string src = node.getProperty("src", std::string());
651 if (check(!src.empty(), "Theme: 'img' element has empty 'src' attribute!"))
652 return;
653
654 auto image = getImage(src);
655 if (check(image, "Theme: Failed to load image '%s'!", src.c_str()))
656 return;
657
658 int left = 0;
659 int right = 0;
660 int top = 0;
661 int bottom = 0;
662 int x = 0;
663 int y = 0;
664 int width = image->getWidth();
665 int height = image->getHeight();
666
667 node.attribute("left", left);
668 node.attribute("right", right);
669 node.attribute("top", top);
670 node.attribute("bottom", bottom);
671 node.attribute("x", x);
672 node.attribute("y", y);
673 node.attribute("width", width);
674 node.attribute("height", height);
675
676 if (check(left >= 0 || right >= 0 || top >= 0 || bottom >= 0, "Theme: Invalid border value!"))
677 return;
678 if (check(x >= 0 || y >= 0, "Theme: Invalid position value!"))
679 return;
680 if (check(width >= 0 || height >= 0, "Theme: Invalid size value!"))
681 return;
682 if (check(x + width <= image->getWidth() || y + height <= image->getHeight(), "Theme: Image size out of bounds!"))
683 return;
684
685 auto &part = state.parts.emplace_back();
686
687 node.attribute("offsetX", part.offsetX);
688 node.attribute("offsetY", part.offsetY);
689
690 if (left + right + top + bottom > 0)
691 {
692 auto &border = part.data.emplace<ImageRect>();
693 border.left = left;
694 border.right = right;
695 border.top = top;
696 border.bottom = bottom;
697 border.image.reset(image->getSubImage(x, y, width, height));
698
699 node.attribute("fill", border.fillMode);
700 }
701 else
702 {
703 part.data = image->getSubImage(x, y, width, height);
704 }
705}
706
707template<>
708inline void fromString(const char *str, gcn::Color &value)
709{
710 if (strlen(str) < 7 || str[0] != '#')
711 {
712 error:
713 Log::info("Error, invalid theme color palette: %s", str);
714 value = gcn::Color(0, 0, 0);
715 return;
716 }
717
718 int v = 0;
719 for (int i = 1; i < 7; ++i)
720 {
721 char c = str[i];
722 int n;
723
724 if ('0' <= c && c <= '9')
725 n = c - '0';
726 else if ('A' <= c && c <= 'F')
727 n = c - 'A' + 10;
728 else if ('a' <= c && c <= 'f')
729 n = c - 'a' + 10;
730 else
731 goto error;
732
733 v = (v << 4) | n;
734 }
735
736 value = gcn::Color(v);
737}
738
740{
741 std::string name;
742 std::string src;
743 node.attribute("name", name);
744 node.attribute("src", src);
745
746 if (check(!name.empty(), "Theme: 'icon' element has empty 'name' attribute!"))
747 return;
748 if (check(!src.empty(), "Theme: 'icon' element has empty 'src' attribute!"))
749 return;
750
751 auto image = getImage(src);
752 if (check(image, "Theme: Failed to load image '%s'!", src.c_str()))
753 return;
754
755 int x = 0;
756 int y = 0;
757 int width = image->getWidth();
758 int height = image->getHeight();
759
760 node.attribute("x", x);
761 node.attribute("y", y);
762 node.attribute("width", width);
763 node.attribute("height", height);
764
765 if (check(x >= 0 || y >= 0, "Theme: Invalid position value!"))
766 return;
767 if (check(width >= 0 || height >= 0, "Theme: Invalid size value!"))
768 return;
769 if (check(x + width <= image->getWidth() || y + height <= image->getHeight(), "Theme: Image size out of bounds!"))
770 return;
771
772 mIcons[name] = image->getSubImage(x, y, width, height);
773}
774
775static int readColorId(const std::string &id)
776{
777 static constexpr const char *colors[Theme::THEME_COLORS_END] = {
778 "TEXT",
779 "BLACK",
780 "RED",
781 "GREEN",
782 "BLUE",
783 "ORANGE",
784 "YELLOW",
785 "PINK",
786 "PURPLE",
787 "GRAY",
788 "BROWN",
789 "CARET",
790 "SHADOW",
791 "OUTLINE",
792 "PARTY_TAB",
793 "WHISPER_TAB",
794 "BACKGROUND",
795 "HIGHLIGHT",
796 "HIGHLIGHT_TEXT",
797 "TAB_FLASH",
798 "SHOP_WARNING",
799 "ITEM_EQUIPPED",
800 "CHAT",
801 "OLDCHAT",
802 "AWAYCHAT",
803 "BUBBLE_TEXT",
804 "GM",
805 "GLOBAL",
806 "PLAYER",
807 "WHISPER",
808 "IS",
809 "PARTY",
810 "GUILD",
811 "SERVER",
812 "LOGGER",
813 "HYPERLINK",
814 "UNKNOWN_ITEM",
815 "GENERIC",
816 "HEAD",
817 "USABLE",
818 "TORSO",
819 "ONEHAND",
820 "LEGS",
821 "FEET",
822 "TWOHAND",
823 "SHIELD",
824 "RING",
825 "NECKLACE",
826 "ARMS",
827 "AMMO",
828 "SERVER_VERSION_NOT_SUPPORTED"
829 };
830
831 if (id.empty())
832 return -1;
833
834 for (int i = 0; i < Theme::THEME_COLORS_END; i++)
835 if (id == colors[i])
836 return i;
837
838 return -1;
839}
840
841static Palette::GradientType readGradientType(const std::string &grad)
842{
843 static constexpr const char *grads[] = {
844 "STATIC",
845 "PULSE",
846 "SPECTRUM",
847 "RAINBOW"
848 };
849
850 if (grad.empty())
851 return Palette::STATIC;
852
853 for (int i = 0; i < 4; i++)
854 if (grad == grads[i])
855 return static_cast<Palette::GradientType>(i);
856
857 return Palette::STATIC;
858}
859
860static void readColorNode(XML::Node node, Palette &palette)
861{
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()))
865 return;
866
867 gcn::Color color;
868 if (check(node.attribute("color", color), "Theme: 'color' element missing 'color' attribute!"))
869 return;
870
871 std::optional<gcn::Color> outlineColor;
872 node.attribute("outlineColor", outlineColor);
873
874 const auto grad = readGradientType(node.getProperty("effect", std::string()));
875 palette.setColor(id, color, outlineColor, grad, 10);
876}
877
879{
880 int paletteId;
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);
883
884 Palette &palette = mPalettes.emplace_back(THEME_COLORS_END);
885
886 for (auto childNode : node.children())
887 {
888 if (childNode.name() == "color")
889 readColorNode(childNode, palette);
890 else
891 Log::info("Theme: Unknown node '%s'!", childNode.name().data());
892 }
893}
894
895static int readProgressId(const std::string &id)
896{
897 static constexpr const char *colors[Theme::THEME_PROG_END] = {
898 "DEFAULT",
899 "HP",
900 "MP",
901 "NO_MP",
902 "EXP",
903 "INVY_SLOTS",
904 "WEIGHT",
905 "JOB"
906 };
907
908 if (id.empty())
909 return -1;
910
911 for (int i = 0; i < Theme::THEME_PROG_END; i++)
912 if (id == colors[i])
913 return i;
914
915 return -1;
916}
917
919{
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()))
923 return;
924
925 std::string color;
926 if (node.attribute("color", color))
927 mProgressColors[id] = std::make_unique<DyePalette>(color);
928
929 for (auto childNode : node.children())
930 {
931 if (childNode.name() == "text")
932 readTextNode(childNode, mProgressTextFormats[id].emplace());
933 else
934 Log::info("Theme: Unknown node '%s' in progressbar!", childNode.name().data());
935 }
936}
std::string getStringValue(const std::string &key) const
void listen(Event::Channel channel)
Definition event.h:42
@ ConfigOptionChanged
Definition event.h:68
Channel
Definition event.h:45
@ ConfigChannel
Definition event.h:51
A central point of control for graphics.
Definition graphics.h:78
gcn::Font * getFont() const
Definition graphics.h:248
void drawImageRect(const ImageRect &imgRect, int x, int y, int w, int h)
Draws a rectangle using images.
Definition graphics.cpp:123
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={})
Definition graphics.cpp:176
void setColor(const gcn::Color &color) override
Definition graphics.h:255
const gcn::Color & getColor() const final
Definition graphics.h:260
bool drawImage(const Image *image, int x, int y)
Blits an image onto the screen.
Definition graphics.cpp:36
gcn::Font * getFont() const
Return game font.
Definition gui.h:107
Theme * getTheme() const
The global GUI theme.
Definition gui.h:138
Defines a class for loading and storing images.
Definition image.h:45
Class controlling the game's color palette.
Definition palette.h:42
const gcn::Color & getColor(int type) const
Gets the color associated with the type.
Definition palette.h:72
void setColor(int type, const gcn::Color &color, const std::optional< gcn::Color > &outlineColor, GradientType grad, int delay)
Definition palette.cpp:71
GradientType
Colors can be static or can alter over time.
Definition palette.h:53
@ STATIC
Definition palette.h:54
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.
Definition resource.h:74
Definition theme.h:154
~Skin()
Definition theme.cpp:108
void updateAlpha(float alpha)
Updates the alpha value of the skin.
Definition theme.cpp:213
void draw(Graphics *graphics, const WidgetState &state) const
Definition theme.cpp:122
void addState(SkinState state)
Definition theme.cpp:117
int getMinWidth() const
Returns the minimum width which can be used with this skin.
Definition theme.cpp:177
std::vector< SkinState > mStates
Definition theme.h:192
const SkinState * getState(uint8_t flags) const
Definition theme.cpp:168
int getMinHeight() const
Returns the minimum height which can be used with this skin.
Definition theme.cpp:195
std::string path
Definition theme.h:64
const std::string & getPath() const
Definition theme.h:58
const std::string & getName() const
Definition theme.h:57
std::unique_ptr< XML::Document > doc
Definition theme.h:65
const XML::Document & getDocument() const
Definition theme.h:60
ThemeInfo()=default
std::string getFullPath() const
Definition theme.cpp:80
std::string name
Definition theme.h:63
void readSkinStateNode(XML::Node node, Skin &skin) const
Definition theme.cpp:605
Theme(const ThemeInfo &themeInfo)
Definition theme.cpp:228
float mAlpha
Definition theme.h:361
static std::string prepareThemePath()
Definition theme.cpp:250
bool readTheme(const ThemeInfo &themeInfo)
Definition theme.cpp:493
float mMinimumOpacity
Tells if the current skins opacity should not get less than the given value.
Definition theme.h:360
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,...
Definition theme.cpp:284
const Skin & getSkin(SkinType skinType) const
Definition theme.cpp:434
static const gcn::Color & getThemeColor(int type)
Gets the color associated with the type in the default palette (0).
Definition theme.cpp:313
std::array< std::unique_ptr< DyePalette >, THEME_PROG_END > mProgressColors
Definition theme.h:364
std::string mThemePath
Definition theme.h:352
ResourceRef< Image > getImage(const std::string &path) const
Definition theme.cpp:303
std::vector< Palette > mPalettes
Definition theme.h:363
const Palette & getPalette(size_t index) const
Definition theme.cpp:328
void updateAlpha()
Updates the alpha values of all of the skins and images.
Definition theme.cpp:459
static ResourceRef< Image > getImageFromTheme(const std::string &path)
Definition theme.cpp:308
~Theme() override
Definition theme.cpp:244
void readProgressBarNode(XML::Node node)
Definition theme.cpp:918
static std::optional< int > getColorIdForChar(char c)
Returns the color ID associated with a character, if it exists.
Definition theme.cpp:338
void readSkinNode(XML::Node node)
Definition theme.cpp:562
void readIconNode(XML::Node node)
Definition theme.cpp:739
void readSkinStateImgNode(XML::Node node, SkinState &state) const
Definition theme.cpp:648
void setMinimumOpacity(float minimumOpacity)
Set the minimum opacity allowed to skins.
Definition theme.cpp:450
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
Definition theme.cpp:378
static std::vector< ThemeInfo > getAvailableThemes()
Definition theme.cpp:265
ProgressPalette
Definition theme.h:268
@ THEME_PROG_END
Definition theme.h:277
const gcn::Color & getColor(int type) const
Returns a color from the default palette (0).
Definition theme.cpp:333
static gcn::Color getProgressColor(int type, float progress)
Definition theme.cpp:318
@ HYPERLINK
Definition theme.h:249
@ PINK
Definition theme.h:221
@ LOGGER
Definition theme.h:248
@ PLAYER
Definition theme.h:242
@ OLDCHAT
Definition theme.h:237
@ IS
Definition theme.h:244
@ BLACK
Definition theme.h:215
@ SERVER
Definition theme.h:247
@ GREEN
Definition theme.h:217
@ HIGHLIGHT
Definition theme.h:231
@ RED
Definition theme.h:216
@ GLOBAL
Definition theme.h:241
@ THEME_COLORS_END
Definition theme.h:265
@ AWAYCHAT
Definition theme.h:238
@ YELLOW
Definition theme.h:220
@ GRAY
Definition theme.h:223
@ BLUE
Definition theme.h:218
@ ORANGE
Definition theme.h:219
@ CHAT
Definition theme.h:236
@ GUILD
Definition theme.h:246
@ PURPLE
Definition theme.h:222
@ WHISPER
Definition theme.h:243
@ BROWN
Definition theme.h:224
@ PARTY
Definition theme.h:245
@ GM
Definition theme.h:240
const Image * getIcon(const std::string &name) const
Definition theme.cpp:441
void event(Event::Channel channel, const Event &event) override
Definition theme.cpp:471
void readPaletteNode(XML::Node node)
Definition theme.cpp:878
std::map< std::string, Image * > mIcons
Definition theme.h:354
void drawSkin(Graphics *graphics, SkinType type, const WidgetState &state) const
Definition theme.cpp:373
std::array< std::optional< TextFormat >, THEME_PROG_END > mProgressTextFormats
Definition theme.h:365
std::map< SkinType, Skin > mSkins
Definition theme.h:353
Node rootNode() const
Returns the root node of the document (or NULL if there was a load error).
Definition xml.h:213
std::string_view name() const
Definition xml.h:46
int getProperty(const char *name, int def) const
Definition xml.h:144
Children children() const
Definition xml.h:97
bool attribute(const char *name, T &value) const
Definition xml.h:129
Config config
Global settings (config.xml)
Definition client.cpp:97
Graphics * graphics
Definition client.cpp:104
Configuration branding
XML branding information reader.
Definition client.cpp:98
#define _(s)
Definition gettext.h:38
FillMode
Definition graphics.h:37
Gui * gui
The GUI system.
Definition gui.cpp:50
gcn::Font * boldFont
Bolded text font.
Definition gui.cpp:54
bool isDirectory(const std::string &path)
Checks whether the given path is a directory.
Definition filesystem.h:107
bool exists(const std::string &path)
Checks whether the given file or directory exists in the search path.
Definition filesystem.h:93
Files enumerateFiles(const std::string &dir)
Returns a list of files in the given directory.
Definition filesystem.h:153
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)
unsigned char uint8_t
Definition sha256.cpp:81
float guiAlpha
std::string theme
An image reference along with the margins specifying how to render this image at different sizes.
Definition graphics.h:62
int left
Definition graphics.h:65
uint8_t stateFlags
Definition theme.h:134
std::vector< SkinPart > parts
Definition theme.h:137
uint8_t setFlags
Definition theme.h:135
TextFormat textFormat
Definition theme.h:136
std::optional< gcn::Color > shadowColor
Definition theme.h:129
std::optional< gcn::Color > outlineColor
Definition theme.h:128
bool bold
Definition theme.h:126
gcn::Color color
Definition theme.h:127
uint8_t flags
Definition theme.h:150
WidgetState()=default
int width
Definition theme.h:148
int height
Definition theme.h:149
void fromString(const char *str, FillMode &value)
Definition theme.cpp:640
@ STATE_HOVERED
Definition theme.h:105
@ STATE_DISABLED
Definition theme.h:107
@ STATE_FOCUSED
Definition theme.h:108
@ STATE_SELECTED
Definition theme.h:106
SkinType
Definition theme.h:69
@ DropDownButton
@ ScrollAreaHBar
@ ScrollAreaHMarker
@ ScrollAreaVMarker
@ ScrollAreaVBar