StyleSpec.h 11 KB

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