1 /* 2 Minetest 3 Copyright (C) 2019 rubenwardy 4 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. 9 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. 14 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. 18 */ 19 20 #include "client/tile.h" // ITextureSource 21 #include "client/fontengine.h" 22 #include "debug.h" 23 #include "irrlichttypes_extrabloated.h" 24 #include "util/string.h" 25 #include <algorithm> 26 #include <array> 27 #include <vector> 28 29 #pragma once 30 31 class StyleSpec 32 { 33 public: 34 enum Property 35 { 36 TEXTCOLOR, 37 BGCOLOR, 38 BGCOLOR_HOVERED, // Note: Deprecated property 39 BGCOLOR_PRESSED, // Note: Deprecated property 40 NOCLIP, 41 BORDER, 42 BGIMG, 43 BGIMG_HOVERED, // Note: Deprecated property 44 BGIMG_MIDDLE, 45 BGIMG_PRESSED, // Note: Deprecated property 46 FGIMG, 47 FGIMG_HOVERED, // Note: Deprecated property 48 FGIMG_PRESSED, // Note: Deprecated property 49 ALPHA, 50 CONTENT_OFFSET, 51 PADDING, 52 FONT, 53 FONT_SIZE, 54 COLORS, 55 BORDERCOLORS, 56 BORDERWIDTHS, 57 SOUND, 58 SPACING, 59 SIZE, 60 NUM_PROPERTIES, 61 NONE 62 }; 63 enum State 64 { 65 STATE_DEFAULT = 0, 66 STATE_HOVERED = 1 << 0, 67 STATE_PRESSED = 1 << 1, 68 NUM_STATES = 1 << 2, 69 STATE_INVALID = 1 << 3, 70 }; 71 72 private: 73 std::array<bool, NUM_PROPERTIES> property_set{}; 74 std::array<std::string, NUM_PROPERTIES> properties; 75 State state_map = STATE_DEFAULT; 76 77 public: GetPropertyByName(const std::string & name)78 static Property GetPropertyByName(const std::string &name) 79 { 80 if (name == "textcolor") { 81 return TEXTCOLOR; 82 } else if (name == "bgcolor") { 83 return BGCOLOR; 84 } else if (name == "bgcolor_hovered") { 85 return BGCOLOR_HOVERED; 86 } else if (name == "bgcolor_pressed") { 87 return BGCOLOR_PRESSED; 88 } else if (name == "noclip") { 89 return NOCLIP; 90 } else if (name == "border") { 91 return BORDER; 92 } else if (name == "bgimg") { 93 return BGIMG; 94 } else if (name == "bgimg_hovered") { 95 return BGIMG_HOVERED; 96 } else if (name == "bgimg_middle") { 97 return BGIMG_MIDDLE; 98 } else if (name == "bgimg_pressed") { 99 return BGIMG_PRESSED; 100 } else if (name == "fgimg") { 101 return FGIMG; 102 } else if (name == "fgimg_hovered") { 103 return FGIMG_HOVERED; 104 } else if (name == "fgimg_pressed") { 105 return FGIMG_PRESSED; 106 } else if (name == "alpha") { 107 return ALPHA; 108 } else if (name == "content_offset") { 109 return CONTENT_OFFSET; 110 } else if (name == "padding") { 111 return PADDING; 112 } else if (name == "font") { 113 return FONT; 114 } else if (name == "font_size") { 115 return FONT_SIZE; 116 } else if (name == "colors") { 117 return COLORS; 118 } else if (name == "bordercolors") { 119 return BORDERCOLORS; 120 } else if (name == "borderwidths") { 121 return BORDERWIDTHS; 122 } else if (name == "sound") { 123 return SOUND; 124 } else if (name == "spacing") { 125 return SPACING; 126 } else if (name == "size") { 127 return SIZE; 128 } else { 129 return NONE; 130 } 131 } 132 get(Property prop,std::string def)133 std::string get(Property prop, std::string def) const 134 { 135 const auto &val = properties[prop]; 136 return val.empty() ? def : val; 137 } 138 set(Property prop,const std::string & value)139 void set(Property prop, const std::string &value) 140 { 141 properties[prop] = value; 142 property_set[prop] = true; 143 } 144 145 //! Parses a name and returns the corresponding state enum getStateByName(const std::string & name)146 static State getStateByName(const std::string &name) 147 { 148 if (name == "default") { 149 return STATE_DEFAULT; 150 } else if (name == "hovered") { 151 return STATE_HOVERED; 152 } else if (name == "pressed") { 153 return STATE_PRESSED; 154 } else { 155 return STATE_INVALID; 156 } 157 } 158 159 //! Gets the state that this style is intended for getState()160 State getState() const 161 { 162 return state_map; 163 } 164 165 //! Set the given state on this style addState(State state)166 void addState(State state) 167 { 168 FATAL_ERROR_IF(state >= NUM_STATES, "Out-of-bounds state received"); 169 170 state_map = static_cast<State>(state_map | state); 171 } 172 173 //! Using a list of styles mapped to state values, calculate the final 174 // combined style for a state by propagating values in its component states getStyleFromStatePropagation(const std::array<StyleSpec,NUM_STATES> & styles,State state)175 static StyleSpec getStyleFromStatePropagation(const std::array<StyleSpec, NUM_STATES> &styles, State state) 176 { 177 StyleSpec temp = styles[StyleSpec::STATE_DEFAULT]; 178 temp.state_map = state; 179 for (int i = StyleSpec::STATE_DEFAULT + 1; i <= state; i++) { 180 if ((state & i) != 0) { 181 temp = temp | styles[i]; 182 } 183 } 184 185 return temp; 186 } 187 getColor(Property prop,video::SColor def)188 video::SColor getColor(Property prop, video::SColor def) const 189 { 190 const auto &val = properties[prop]; 191 if (val.empty()) { 192 return def; 193 } 194 195 parseColorString(val, def, false, 0xFF); 196 return def; 197 } 198 getColor(Property prop)199 video::SColor getColor(Property prop) const 200 { 201 const auto &val = properties[prop]; 202 FATAL_ERROR_IF(val.empty(), "Unexpected missing property"); 203 204 video::SColor color; 205 parseColorString(val, color, false, 0xFF); 206 return color; 207 } 208 getColorArray(Property prop,std::array<video::SColor,4> def)209 std::array<video::SColor, 4> getColorArray(Property prop, 210 std::array<video::SColor, 4> def) const 211 { 212 const auto &val = properties[prop]; 213 if (val.empty()) 214 return def; 215 216 std::vector<std::string> strs; 217 if (!parseArray(val, strs)) 218 return def; 219 220 for (size_t i = 0; i <= 3; i++) { 221 video::SColor color; 222 if (parseColorString(strs[i], color, false, 0xff)) 223 def[i] = color; 224 } 225 226 return def; 227 } 228 getIntArray(Property prop,std::array<s32,4> def)229 std::array<s32, 4> getIntArray(Property prop, std::array<s32, 4> def) const 230 { 231 const auto &val = properties[prop]; 232 if (val.empty()) 233 return def; 234 235 std::vector<std::string> strs; 236 if (!parseArray(val, strs)) 237 return def; 238 239 for (size_t i = 0; i <= 3; i++) 240 def[i] = stoi(strs[i]); 241 242 return def; 243 } 244 getRect(Property prop,irr::core::rect<s32> def)245 irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const 246 { 247 const auto &val = properties[prop]; 248 if (val.empty()) 249 return def; 250 251 irr::core::rect<s32> rect; 252 if (!parseRect(val, &rect)) 253 return def; 254 255 return rect; 256 } 257 getRect(Property prop)258 irr::core::rect<s32> getRect(Property prop) const 259 { 260 const auto &val = properties[prop]; 261 FATAL_ERROR_IF(val.empty(), "Unexpected missing property"); 262 263 irr::core::rect<s32> rect; 264 parseRect(val, &rect); 265 return rect; 266 } 267 getVector2f(Property prop,v2f32 def)268 v2f32 getVector2f(Property prop, v2f32 def) const 269 { 270 const auto &val = properties[prop]; 271 if (val.empty()) 272 return def; 273 274 v2f32 vec; 275 if (!parseVector2f(val, &vec)) 276 return def; 277 278 return vec; 279 } 280 getVector2i(Property prop,v2s32 def)281 v2s32 getVector2i(Property prop, v2s32 def) const 282 { 283 const auto &val = properties[prop]; 284 if (val.empty()) 285 return def; 286 287 v2f32 vec; 288 if (!parseVector2f(val, &vec)) 289 return def; 290 291 return v2s32(vec.X, vec.Y); 292 } 293 getVector2i(Property prop)294 v2s32 getVector2i(Property prop) const 295 { 296 const auto &val = properties[prop]; 297 FATAL_ERROR_IF(val.empty(), "Unexpected missing property"); 298 299 v2f32 vec; 300 parseVector2f(val, &vec); 301 return v2s32(vec.X, vec.Y); 302 } 303 getFont()304 gui::IGUIFont *getFont() const 305 { 306 FontSpec spec(FONT_SIZE_UNSPECIFIED, FM_Standard, false, false); 307 308 const std::string &font = properties[FONT]; 309 const std::string &size = properties[FONT_SIZE]; 310 311 if (font.empty() && size.empty()) 312 return nullptr; 313 314 std::vector<std::string> modes = split(font, ','); 315 316 for (size_t i = 0; i < modes.size(); i++) { 317 if (modes[i] == "normal") 318 spec.mode = FM_Standard; 319 else if (modes[i] == "mono") 320 spec.mode = FM_Mono; 321 else if (modes[i] == "bold") 322 spec.bold = true; 323 else if (modes[i] == "italic") 324 spec.italic = true; 325 } 326 327 if (!size.empty()) { 328 int calc_size = 1; 329 330 if (size[0] == '*') { 331 std::string new_size = size.substr(1); // Remove '*' (invalid for stof) 332 calc_size = stof(new_size) * g_fontengine->getFontSize(spec.mode); 333 } else if (size[0] == '+' || size[0] == '-') { 334 calc_size = stoi(size) + g_fontengine->getFontSize(spec.mode); 335 } else { 336 calc_size = stoi(size); 337 } 338 339 spec.size = (unsigned)std::min(std::max(calc_size, 1), 999); 340 } 341 342 return g_fontengine->getFont(spec); 343 } 344 getTexture(Property prop,ISimpleTextureSource * tsrc,video::ITexture * def)345 video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc, 346 video::ITexture *def) const 347 { 348 const auto &val = properties[prop]; 349 if (val.empty()) { 350 return def; 351 } 352 353 video::ITexture *texture = tsrc->getTexture(val); 354 355 return texture; 356 } 357 getTexture(Property prop,ISimpleTextureSource * tsrc)358 video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc) const 359 { 360 const auto &val = properties[prop]; 361 FATAL_ERROR_IF(val.empty(), "Unexpected missing property"); 362 363 video::ITexture *texture = tsrc->getTexture(val); 364 365 return texture; 366 } 367 getBool(Property prop,bool def)368 bool getBool(Property prop, bool def) const 369 { 370 const auto &val = properties[prop]; 371 if (val.empty()) { 372 return def; 373 } 374 375 return is_yes(val); 376 } 377 isNotDefault(Property prop)378 inline bool isNotDefault(Property prop) const 379 { 380 return !properties[prop].empty(); 381 } 382 hasProperty(Property prop)383 inline bool hasProperty(Property prop) const { return property_set[prop]; } 384 385 StyleSpec &operator|=(const StyleSpec &other) 386 { 387 for (size_t i = 0; i < NUM_PROPERTIES; i++) { 388 auto prop = (Property)i; 389 if (other.hasProperty(prop)) { 390 set(prop, other.get(prop, "")); 391 } 392 } 393 394 return *this; 395 } 396 397 StyleSpec operator|(const StyleSpec &other) const 398 { 399 StyleSpec newspec = *this; 400 newspec |= other; 401 return newspec; 402 } 403 404 private: parseArray(const std::string & value,std::vector<std::string> & arr)405 bool parseArray(const std::string &value, std::vector<std::string> &arr) const 406 { 407 std::vector<std::string> strs = split(value, ','); 408 409 if (strs.size() == 1) { 410 arr = {strs[0], strs[0], strs[0], strs[0]}; 411 } else if (strs.size() == 2) { 412 arr = {strs[0], strs[1], strs[0], strs[1]}; 413 } else if (strs.size() == 4) { 414 arr = strs; 415 } else { 416 warningstream << "Invalid array size (" << strs.size() 417 << " arguments): \"" << value << "\"" << std::endl; 418 return false; 419 } 420 return true; 421 } 422 parseRect(const std::string & value,irr::core::rect<s32> * parsed_rect)423 bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const 424 { 425 irr::core::rect<s32> rect; 426 std::vector<std::string> v_rect = split(value, ','); 427 428 if (v_rect.size() == 1) { 429 s32 x = stoi(v_rect[0]); 430 rect.UpperLeftCorner = irr::core::vector2di(x, x); 431 rect.LowerRightCorner = irr::core::vector2di(-x, -x); 432 } else if (v_rect.size() == 2) { 433 s32 x = stoi(v_rect[0]); 434 s32 y = stoi(v_rect[1]); 435 rect.UpperLeftCorner = irr::core::vector2di(x, y); 436 rect.LowerRightCorner = irr::core::vector2di(-x, -y); 437 // `-x` is interpreted as `w - x` 438 } else if (v_rect.size() == 4) { 439 rect.UpperLeftCorner = irr::core::vector2di( 440 stoi(v_rect[0]), stoi(v_rect[1])); 441 rect.LowerRightCorner = irr::core::vector2di( 442 stoi(v_rect[2]), stoi(v_rect[3])); 443 } else { 444 warningstream << "Invalid rectangle string format: \"" << value 445 << "\"" << std::endl; 446 return false; 447 } 448 449 *parsed_rect = rect; 450 451 return true; 452 } 453 parseVector2f(const std::string & value,v2f32 * parsed_vec)454 bool parseVector2f(const std::string &value, v2f32 *parsed_vec) const 455 { 456 v2f32 vec; 457 std::vector<std::string> v_vector = split(value, ','); 458 459 if (v_vector.size() == 1) { 460 f32 x = stof(v_vector[0]); 461 vec.X = x; 462 vec.Y = x; 463 } else if (v_vector.size() == 2) { 464 vec.X = stof(v_vector[0]); 465 vec.Y = stof(v_vector[1]); 466 } else { 467 warningstream << "Invalid 2d vector string format: \"" << value 468 << "\"" << std::endl; 469 return false; 470 } 471 472 *parsed_vec = vec; 473 474 return true; 475 } 476 }; 477