StyleSpec.h 11 KB

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