3 Copyright (C) 2019 rubenwardy
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "client/tile.h" // ITextureSource
21 #include "client/fontengine.h"
23 #include "irrlichttypes_extrabloated.h"
24 #include "util/string.h"
38 BGCOLOR_HOVERED, // Note: Deprecated property
39 BGCOLOR_PRESSED, // Note: Deprecated property
43 BGIMG_HOVERED, // Note: Deprecated property
45 BGIMG_PRESSED, // Note: Deprecated property
47 FGIMG_HOVERED, // Note: Deprecated property
48 FGIMG_PRESSED, // Note: Deprecated property
64 STATE_HOVERED = 1 << 0,
65 STATE_PRESSED = 1 << 1,
67 STATE_INVALID = 1 << 3,
71 std::array<bool, NUM_PROPERTIES> property_set{};
72 std::array<std::string, NUM_PROPERTIES> properties;
73 State state_map = STATE_DEFAULT;
76 static Property GetPropertyByName(const std::string &name)
78 if (name == "textcolor") {
80 } else if (name == "bgcolor") {
82 } else if (name == "bgcolor_hovered") {
83 return BGCOLOR_HOVERED;
84 } else if (name == "bgcolor_pressed") {
85 return BGCOLOR_PRESSED;
86 } else if (name == "noclip") {
88 } else if (name == "border") {
90 } else if (name == "bgimg") {
92 } else if (name == "bgimg_hovered") {
94 } else if (name == "bgimg_middle") {
96 } else if (name == "bgimg_pressed") {
98 } else if (name == "fgimg") {
100 } else if (name == "fgimg_hovered") {
101 return FGIMG_HOVERED;
102 } else if (name == "fgimg_pressed") {
103 return FGIMG_PRESSED;
104 } else if (name == "alpha") {
106 } else if (name == "content_offset") {
107 return CONTENT_OFFSET;
108 } else if (name == "padding") {
110 } else if (name == "font") {
112 } else if (name == "font_size") {
114 } else if (name == "colors") {
116 } else if (name == "bordercolors") {
118 } else if (name == "borderwidths") {
120 } else if (name == "sound") {
127 std::string get(Property prop, std::string def) const
129 const auto &val = properties[prop];
130 return val.empty() ? def : val;
133 void set(Property prop, const std::string &value)
135 properties[prop] = value;
136 property_set[prop] = true;
139 //! Parses a name and returns the corresponding state enum
140 static State getStateByName(const std::string &name)
142 if (name == "default") {
143 return STATE_DEFAULT;
144 } else if (name == "hovered") {
145 return STATE_HOVERED;
146 } else if (name == "pressed") {
147 return STATE_PRESSED;
149 return STATE_INVALID;
153 //! Gets the state that this style is intended for
154 State getState() const
159 //! Set the given state on this style
160 void addState(State state)
162 FATAL_ERROR_IF(state >= NUM_STATES, "Out-of-bounds state received");
164 state_map = static_cast<State>(state_map | state);
167 //! Using a list of styles mapped to state values, calculate the final
168 // combined style for a state by propagating values in its component states
169 static StyleSpec getStyleFromStatePropagation(const std::array<StyleSpec, NUM_STATES> &styles, State state)
171 StyleSpec temp = styles[StyleSpec::STATE_DEFAULT];
172 temp.state_map = state;
173 for (int i = StyleSpec::STATE_DEFAULT + 1; i <= state; i++) {
174 if ((state & i) != 0) {
175 temp = temp | styles[i];
182 video::SColor getColor(Property prop, video::SColor def) const
184 const auto &val = properties[prop];
189 parseColorString(val, def, false, 0xFF);
193 video::SColor getColor(Property prop) const
195 const auto &val = properties[prop];
196 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
199 parseColorString(val, color, false, 0xFF);
203 std::array<video::SColor, 4> getColorArray(Property prop,
204 std::array<video::SColor, 4> def) const
206 const auto &val = properties[prop];
210 std::vector<std::string> strs;
211 if (!parseArray(val, strs))
214 for (size_t i = 0; i <= 3; i++) {
216 if (parseColorString(strs[i], color, false, 0xff))
223 std::array<s32, 4> getIntArray(Property prop, std::array<s32, 4> def) const
225 const auto &val = properties[prop];
229 std::vector<std::string> strs;
230 if (!parseArray(val, strs))
233 for (size_t i = 0; i <= 3; i++)
234 def[i] = stoi(strs[i]);
239 irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const
241 const auto &val = properties[prop];
245 irr::core::rect<s32> rect;
246 if (!parseRect(val, &rect))
252 irr::core::rect<s32> getRect(Property prop) const
254 const auto &val = properties[prop];
255 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
257 irr::core::rect<s32> rect;
258 parseRect(val, &rect);
262 irr::core::vector2d<s32> getVector2i(Property prop, irr::core::vector2d<s32> def) const
264 const auto &val = properties[prop];
268 irr::core::vector2d<s32> vec;
269 if (!parseVector2i(val, &vec))
275 irr::core::vector2d<s32> getVector2i(Property prop) const
277 const auto &val = properties[prop];
278 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
280 irr::core::vector2d<s32> vec;
281 parseVector2i(val, &vec);
285 gui::IGUIFont *getFont() const
287 FontSpec spec(FONT_SIZE_UNSPECIFIED, FM_Standard, false, false);
289 const std::string &font = properties[FONT];
290 const std::string &size = properties[FONT_SIZE];
292 if (font.empty() && size.empty())
295 std::vector<std::string> modes = split(font, ',');
297 for (size_t i = 0; i < modes.size(); i++) {
298 if (modes[i] == "normal")
299 spec.mode = FM_Standard;
300 else if (modes[i] == "mono")
302 else if (modes[i] == "bold")
304 else if (modes[i] == "italic")
311 if (size[0] == '*') {
312 std::string new_size = size.substr(1); // Remove '*' (invalid for stof)
313 calc_size = stof(new_size) * g_fontengine->getFontSize(spec.mode);
314 } else if (size[0] == '+' || size[0] == '-') {
315 calc_size = stoi(size) + g_fontengine->getFontSize(spec.mode);
317 calc_size = stoi(size);
320 spec.size = (unsigned)std::min(std::max(calc_size, 1), 999);
323 return g_fontengine->getFont(spec);
326 video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc,
327 video::ITexture *def) const
329 const auto &val = properties[prop];
334 video::ITexture *texture = tsrc->getTexture(val);
339 video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc) const
341 const auto &val = properties[prop];
342 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
344 video::ITexture *texture = tsrc->getTexture(val);
349 bool getBool(Property prop, bool def) const
351 const auto &val = properties[prop];
359 inline bool isNotDefault(Property prop) const
361 return !properties[prop].empty();
364 inline bool hasProperty(Property prop) const { return property_set[prop]; }
366 StyleSpec &operator|=(const StyleSpec &other)
368 for (size_t i = 0; i < NUM_PROPERTIES; i++) {
369 auto prop = (Property)i;
370 if (other.hasProperty(prop)) {
371 set(prop, other.get(prop, ""));
378 StyleSpec operator|(const StyleSpec &other) const
380 StyleSpec newspec = *this;
386 bool parseArray(const std::string &value, std::vector<std::string> &arr) const
388 std::vector<std::string> strs = split(value, ',');
390 if (strs.size() == 1) {
391 arr = {strs[0], strs[0], strs[0], strs[0]};
392 } else if (strs.size() == 2) {
393 arr = {strs[0], strs[1], strs[0], strs[1]};
394 } else if (strs.size() == 4) {
397 warningstream << "Invalid array size (" << strs.size()
398 << " arguments): \"" << value << "\"" << std::endl;
404 bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const
406 irr::core::rect<s32> rect;
407 std::vector<std::string> v_rect = split(value, ',');
409 if (v_rect.size() == 1) {
410 s32 x = stoi(v_rect[0]);
411 rect.UpperLeftCorner = irr::core::vector2di(x, x);
412 rect.LowerRightCorner = irr::core::vector2di(-x, -x);
413 } else if (v_rect.size() == 2) {
414 s32 x = stoi(v_rect[0]);
415 s32 y = stoi(v_rect[1]);
416 rect.UpperLeftCorner = irr::core::vector2di(x, y);
417 rect.LowerRightCorner = irr::core::vector2di(-x, -y);
418 // `-x` is interpreted as `w - x`
419 } else if (v_rect.size() == 4) {
420 rect.UpperLeftCorner = irr::core::vector2di(
421 stoi(v_rect[0]), stoi(v_rect[1]));
422 rect.LowerRightCorner = irr::core::vector2di(
423 stoi(v_rect[2]), stoi(v_rect[3]));
425 warningstream << "Invalid rectangle string format: \"" << value
426 << "\"" << std::endl;
435 bool parseVector2i(const std::string &value, irr::core::vector2d<s32> *parsed_vec) const
437 irr::core::vector2d<s32> vec;
438 std::vector<std::string> v_vector = split(value, ',');
440 if (v_vector.size() == 1) {
441 s32 x = stoi(v_vector[0]);
444 } else if (v_vector.size() == 2) {
445 s32 x = stoi(v_vector[0]);
446 s32 y = stoi(v_vector[1]);
450 warningstream << "Invalid vector2d string format: \"" << value
451 << "\"" << std::endl;