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
49 FGIMG_PRESSED, // Note: Deprecated property
67 STATE_HOVERED = 1 << 0,
68 STATE_PRESSED = 1 << 1,
70 STATE_INVALID = 1 << 3,
74 std::array<bool, NUM_PROPERTIES> property_set{};
75 std::array<std::string, NUM_PROPERTIES> properties;
76 State state_map = STATE_DEFAULT;
79 static Property GetPropertyByName(const std::string &name)
81 if (name == "textcolor") {
83 } else if (name == "bgcolor") {
85 } else if (name == "bgcolor_hovered") {
86 return BGCOLOR_HOVERED;
87 } else if (name == "bgcolor_pressed") {
88 return BGCOLOR_PRESSED;
89 } else if (name == "noclip") {
91 } else if (name == "border") {
93 } else if (name == "bgimg") {
95 } else if (name == "bgimg_hovered") {
97 } else if (name == "bgimg_middle") {
99 } else if (name == "bgimg_pressed") {
100 return BGIMG_PRESSED;
101 } else if (name == "fgimg") {
103 } else if (name == "fgimg_hovered") {
104 return FGIMG_HOVERED;
105 } else if (name == "fgimg_middle") {
107 } else if (name == "fgimg_pressed") {
108 return FGIMG_PRESSED;
109 } else if (name == "alpha") {
111 } else if (name == "content_offset") {
112 return CONTENT_OFFSET;
113 } else if (name == "padding") {
115 } else if (name == "font") {
117 } else if (name == "font_size") {
119 } else if (name == "colors") {
121 } else if (name == "bordercolors") {
123 } else if (name == "borderwidths") {
125 } else if (name == "sound") {
127 } else if (name == "spacing") {
129 } else if (name == "size") {
136 std::string get(Property prop, std::string def) const
138 const auto &val = properties[prop];
139 return val.empty() ? def : val;
142 void set(Property prop, const std::string &value)
144 properties[prop] = value;
145 property_set[prop] = true;
148 //! Parses a name and returns the corresponding state enum
149 static State getStateByName(const std::string &name)
151 if (name == "default") {
152 return STATE_DEFAULT;
153 } else if (name == "hovered") {
154 return STATE_HOVERED;
155 } else if (name == "pressed") {
156 return STATE_PRESSED;
158 return STATE_INVALID;
162 //! Gets the state that this style is intended for
163 State getState() const
168 //! Set the given state on this style
169 void addState(State state)
171 FATAL_ERROR_IF(state >= NUM_STATES, "Out-of-bounds state received");
173 state_map = static_cast<State>(state_map | state);
176 //! Using a list of styles mapped to state values, calculate the final
177 // combined style for a state by propagating values in its component states
178 static StyleSpec getStyleFromStatePropagation(const std::array<StyleSpec, NUM_STATES> &styles, State state)
180 StyleSpec temp = styles[StyleSpec::STATE_DEFAULT];
181 temp.state_map = state;
182 for (int i = StyleSpec::STATE_DEFAULT + 1; i <= state; i++) {
183 if ((state & i) != 0) {
184 temp = temp | styles[i];
191 video::SColor getColor(Property prop, video::SColor def) const
193 const auto &val = properties[prop];
198 parseColorString(val, def, false, 0xFF);
202 video::SColor getColor(Property prop) const
204 const auto &val = properties[prop];
205 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
208 parseColorString(val, color, false, 0xFF);
212 std::array<video::SColor, 4> getColorArray(Property prop,
213 std::array<video::SColor, 4> def) const
215 const auto &val = properties[prop];
219 std::vector<std::string> strs;
220 if (!parseArray(val, strs))
223 for (size_t i = 0; i <= 3; i++) {
225 if (parseColorString(strs[i], color, false, 0xff))
232 std::array<s32, 4> getIntArray(Property prop, std::array<s32, 4> def) const
234 const auto &val = properties[prop];
238 std::vector<std::string> strs;
239 if (!parseArray(val, strs))
242 for (size_t i = 0; i <= 3; i++)
243 def[i] = stoi(strs[i]);
248 irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const
250 const auto &val = properties[prop];
254 irr::core::rect<s32> rect;
255 if (!parseRect(val, &rect))
261 irr::core::rect<s32> getRect(Property prop) const
263 const auto &val = properties[prop];
264 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
266 irr::core::rect<s32> rect;
267 parseRect(val, &rect);
271 v2f32 getVector2f(Property prop, v2f32 def) const
273 const auto &val = properties[prop];
278 if (!parseVector2f(val, &vec))
284 v2s32 getVector2i(Property prop, v2s32 def) const
286 const auto &val = properties[prop];
291 if (!parseVector2f(val, &vec))
294 return v2s32(vec.X, vec.Y);
297 v2s32 getVector2i(Property prop) const
299 const auto &val = properties[prop];
300 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
303 parseVector2f(val, &vec);
304 return v2s32(vec.X, vec.Y);
307 gui::IGUIFont *getFont() const
309 FontSpec spec(FONT_SIZE_UNSPECIFIED, FM_Standard, false, false);
311 const std::string &font = properties[FONT];
312 const std::string &size = properties[FONT_SIZE];
314 if (font.empty() && size.empty())
317 std::vector<std::string> modes = split(font, ',');
319 for (size_t i = 0; i < modes.size(); i++) {
320 if (modes[i] == "normal")
321 spec.mode = FM_Standard;
322 else if (modes[i] == "mono")
324 else if (modes[i] == "bold")
326 else if (modes[i] == "italic")
333 if (size[0] == '*') {
334 std::string new_size = size.substr(1); // Remove '*' (invalid for stof)
335 calc_size = stof(new_size) * g_fontengine->getFontSize(spec.mode);
336 } else if (size[0] == '+' || size[0] == '-') {
337 calc_size = stoi(size) + g_fontengine->getFontSize(spec.mode);
339 calc_size = stoi(size);
342 spec.size = (unsigned)std::min(std::max(calc_size, 1), 999);
345 return g_fontengine->getFont(spec);
348 video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc,
349 video::ITexture *def) const
351 const auto &val = properties[prop];
356 video::ITexture *texture = tsrc->getTexture(val);
361 video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc) const
363 const auto &val = properties[prop];
364 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
366 video::ITexture *texture = tsrc->getTexture(val);
371 bool getBool(Property prop, bool def) const
373 const auto &val = properties[prop];
381 inline bool isNotDefault(Property prop) const
383 return !properties[prop].empty();
386 inline bool hasProperty(Property prop) const { return property_set[prop]; }
388 StyleSpec &operator|=(const StyleSpec &other)
390 for (size_t i = 0; i < NUM_PROPERTIES; i++) {
391 auto prop = (Property)i;
392 if (other.hasProperty(prop)) {
393 set(prop, other.get(prop, ""));
400 StyleSpec operator|(const StyleSpec &other) const
402 StyleSpec newspec = *this;
408 bool parseArray(const std::string &value, std::vector<std::string> &arr) const
410 std::vector<std::string> strs = split(value, ',');
412 if (strs.size() == 1) {
413 arr = {strs[0], strs[0], strs[0], strs[0]};
414 } else if (strs.size() == 2) {
415 arr = {strs[0], strs[1], strs[0], strs[1]};
416 } else if (strs.size() == 4) {
419 warningstream << "Invalid array size (" << strs.size()
420 << " arguments): \"" << value << "\"" << std::endl;
426 bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const
428 irr::core::rect<s32> rect;
429 std::vector<std::string> v_rect = split(value, ',');
431 if (v_rect.size() == 1) {
432 s32 x = stoi(v_rect[0]);
433 rect.UpperLeftCorner = irr::core::vector2di(x, x);
434 rect.LowerRightCorner = irr::core::vector2di(-x, -x);
435 } else if (v_rect.size() == 2) {
436 s32 x = stoi(v_rect[0]);
437 s32 y = stoi(v_rect[1]);
438 rect.UpperLeftCorner = irr::core::vector2di(x, y);
439 rect.LowerRightCorner = irr::core::vector2di(-x, -y);
440 // `-x` is interpreted as `w - x`
441 } else if (v_rect.size() == 4) {
442 rect.UpperLeftCorner = irr::core::vector2di(
443 stoi(v_rect[0]), stoi(v_rect[1]));
444 rect.LowerRightCorner = irr::core::vector2di(
445 stoi(v_rect[2]), stoi(v_rect[3]));
447 warningstream << "Invalid rectangle string format: \"" << value
448 << "\"" << std::endl;
457 bool parseVector2f(const std::string &value, v2f32 *parsed_vec) const
460 std::vector<std::string> v_vector = split(value, ',');
462 if (v_vector.size() == 1) {
463 f32 x = stof(v_vector[0]);
466 } else if (v_vector.size() == 2) {
467 vec.X = stof(v_vector[0]);
468 vec.Y = stof(v_vector[1]);
470 warningstream << "Invalid 2d vector string format: \"" << value
471 << "\"" << std::endl;