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
63 STATE_HOVERED = 1 << 0,
64 STATE_PRESSED = 1 << 1,
66 STATE_INVALID = 1 << 3,
70 std::array<bool, NUM_PROPERTIES> property_set{};
71 std::array<std::string, NUM_PROPERTIES> properties;
72 State state_map = STATE_DEFAULT;
75 static Property GetPropertyByName(const std::string &name)
77 if (name == "textcolor") {
79 } else if (name == "bgcolor") {
81 } else if (name == "bgcolor_hovered") {
82 return BGCOLOR_HOVERED;
83 } else if (name == "bgcolor_pressed") {
84 return BGCOLOR_PRESSED;
85 } else if (name == "noclip") {
87 } else if (name == "border") {
89 } else if (name == "bgimg") {
91 } else if (name == "bgimg_hovered") {
93 } else if (name == "bgimg_middle") {
95 } else if (name == "bgimg_pressed") {
97 } else if (name == "fgimg") {
99 } else if (name == "fgimg_hovered") {
100 return FGIMG_HOVERED;
101 } else if (name == "fgimg_pressed") {
102 return FGIMG_PRESSED;
103 } else if (name == "alpha") {
105 } else if (name == "content_offset") {
106 return CONTENT_OFFSET;
107 } else if (name == "padding") {
109 } else if (name == "font") {
111 } else if (name == "font_size") {
113 } else if (name == "colors") {
115 } else if (name == "bordercolors") {
117 } else if (name == "borderwidths") {
124 std::string get(Property prop, std::string def) const
126 const auto &val = properties[prop];
127 return val.empty() ? def : val;
130 void set(Property prop, const std::string &value)
132 properties[prop] = value;
133 property_set[prop] = true;
136 //! Parses a name and returns the corresponding state enum
137 static State getStateByName(const std::string &name)
139 if (name == "default") {
140 return STATE_DEFAULT;
141 } else if (name == "hovered") {
142 return STATE_HOVERED;
143 } else if (name == "pressed") {
144 return STATE_PRESSED;
146 return STATE_INVALID;
150 //! Gets the state that this style is intended for
151 State getState() const
156 //! Set the given state on this style
157 void addState(State state)
159 FATAL_ERROR_IF(state >= NUM_STATES, "Out-of-bounds state received");
161 state_map = static_cast<State>(state_map | state);
164 //! Using a list of styles mapped to state values, calculate the final
165 // combined style for a state by propagating values in its component states
166 static StyleSpec getStyleFromStatePropagation(const std::array<StyleSpec, NUM_STATES> &styles, State state)
168 StyleSpec temp = styles[StyleSpec::STATE_DEFAULT];
169 temp.state_map = state;
170 for (int i = StyleSpec::STATE_DEFAULT + 1; i <= state; i++) {
171 if ((state & i) != 0) {
172 temp = temp | styles[i];
179 video::SColor getColor(Property prop, video::SColor def) const
181 const auto &val = properties[prop];
186 parseColorString(val, def, false, 0xFF);
190 video::SColor getColor(Property prop) const
192 const auto &val = properties[prop];
193 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
196 parseColorString(val, color, false, 0xFF);
200 std::array<video::SColor, 4> getColorArray(Property prop,
201 std::array<video::SColor, 4> def) const
203 const auto &val = properties[prop];
207 std::vector<std::string> strs;
208 if (!parseArray(val, strs))
211 for (size_t i = 0; i <= 3; i++) {
213 if (parseColorString(strs[i], color, false, 0xff))
220 std::array<s32, 4> getIntArray(Property prop, std::array<s32, 4> def) const
222 const auto &val = properties[prop];
226 std::vector<std::string> strs;
227 if (!parseArray(val, strs))
230 for (size_t i = 0; i <= 3; i++)
231 def[i] = stoi(strs[i]);
236 irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const
238 const auto &val = properties[prop];
242 irr::core::rect<s32> rect;
243 if (!parseRect(val, &rect))
249 irr::core::rect<s32> getRect(Property prop) const
251 const auto &val = properties[prop];
252 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
254 irr::core::rect<s32> rect;
255 parseRect(val, &rect);
259 irr::core::vector2d<s32> getVector2i(Property prop, irr::core::vector2d<s32> def) const
261 const auto &val = properties[prop];
265 irr::core::vector2d<s32> vec;
266 if (!parseVector2i(val, &vec))
272 irr::core::vector2d<s32> getVector2i(Property prop) const
274 const auto &val = properties[prop];
275 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
277 irr::core::vector2d<s32> vec;
278 parseVector2i(val, &vec);
282 gui::IGUIFont *getFont() const
284 FontSpec spec(FONT_SIZE_UNSPECIFIED, FM_Standard, false, false);
286 const std::string &font = properties[FONT];
287 const std::string &size = properties[FONT_SIZE];
289 if (font.empty() && size.empty())
292 std::vector<std::string> modes = split(font, ',');
294 for (size_t i = 0; i < modes.size(); i++) {
295 if (modes[i] == "normal")
296 spec.mode = FM_Standard;
297 else if (modes[i] == "mono")
299 else if (modes[i] == "bold")
301 else if (modes[i] == "italic")
308 if (size[0] == '*') {
309 std::string new_size = size.substr(1); // Remove '*' (invalid for stof)
310 calc_size = stof(new_size) * g_fontengine->getFontSize(spec.mode);
311 } else if (size[0] == '+' || size[0] == '-') {
312 calc_size = stoi(size) + g_fontengine->getFontSize(spec.mode);
314 calc_size = stoi(size);
317 spec.size = (unsigned)std::min(std::max(calc_size, 1), 999);
320 return g_fontengine->getFont(spec);
323 video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc,
324 video::ITexture *def) const
326 const auto &val = properties[prop];
331 video::ITexture *texture = tsrc->getTexture(val);
336 video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc) const
338 const auto &val = properties[prop];
339 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
341 video::ITexture *texture = tsrc->getTexture(val);
346 bool getBool(Property prop, bool def) const
348 const auto &val = properties[prop];
356 inline bool isNotDefault(Property prop) const
358 return !properties[prop].empty();
361 inline bool hasProperty(Property prop) const { return property_set[prop]; }
363 StyleSpec &operator|=(const StyleSpec &other)
365 for (size_t i = 0; i < NUM_PROPERTIES; i++) {
366 auto prop = (Property)i;
367 if (other.hasProperty(prop)) {
368 set(prop, other.get(prop, ""));
375 StyleSpec operator|(const StyleSpec &other) const
377 StyleSpec newspec = *this;
383 bool parseArray(const std::string &value, std::vector<std::string> &arr) const
385 std::vector<std::string> strs = split(value, ',');
387 if (strs.size() == 1) {
388 arr = {strs[0], strs[0], strs[0], strs[0]};
389 } else if (strs.size() == 2) {
390 arr = {strs[0], strs[1], strs[0], strs[1]};
391 } else if (strs.size() == 4) {
394 warningstream << "Invalid array size (" << strs.size()
395 << " arguments): \"" << value << "\"" << std::endl;
401 bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const
403 irr::core::rect<s32> rect;
404 std::vector<std::string> v_rect = split(value, ',');
406 if (v_rect.size() == 1) {
407 s32 x = stoi(v_rect[0]);
408 rect.UpperLeftCorner = irr::core::vector2di(x, x);
409 rect.LowerRightCorner = irr::core::vector2di(-x, -x);
410 } else if (v_rect.size() == 2) {
411 s32 x = stoi(v_rect[0]);
412 s32 y = stoi(v_rect[1]);
413 rect.UpperLeftCorner = irr::core::vector2di(x, y);
414 rect.LowerRightCorner = irr::core::vector2di(-x, -y);
415 // `-x` is interpreted as `w - x`
416 } else if (v_rect.size() == 4) {
417 rect.UpperLeftCorner = irr::core::vector2di(
418 stoi(v_rect[0]), stoi(v_rect[1]));
419 rect.LowerRightCorner = irr::core::vector2di(
420 stoi(v_rect[2]), stoi(v_rect[3]));
422 warningstream << "Invalid rectangle string format: \"" << value
423 << "\"" << std::endl;
432 bool parseVector2i(const std::string &value, irr::core::vector2d<s32> *parsed_vec) const
434 irr::core::vector2d<s32> vec;
435 std::vector<std::string> v_vector = split(value, ',');
437 if (v_vector.size() == 1) {
438 s32 x = stoi(v_vector[0]);
441 } else if (v_vector.size() == 2) {
442 s32 x = stoi(v_vector[0]);
443 s32 y = stoi(v_vector[1]);
447 warningstream << "Invalid vector2d string format: \"" << value
448 << "\"" << std::endl;