]> git.lizzy.rs Git - dragonfireclient.git/blob - src/gui/StyleSpec.h
Mark additional locales as broken
[dragonfireclient.git] / src / gui / StyleSpec.h
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                 NUM_PROPERTIES,
58                 NONE
59         };
60         enum State
61         {
62                 STATE_DEFAULT = 0,
63                 STATE_HOVERED = 1 << 0,
64                 STATE_PRESSED = 1 << 1,
65                 NUM_STATES = 1 << 2,
66                 STATE_INVALID = 1 << 3,
67         };
68
69 private:
70         std::array<bool, NUM_PROPERTIES> property_set{};
71         std::array<std::string, NUM_PROPERTIES> properties;
72         State state_map = STATE_DEFAULT;
73
74 public:
75         static Property GetPropertyByName(const std::string &name)
76         {
77                 if (name == "textcolor") {
78                         return TEXTCOLOR;
79                 } else if (name == "bgcolor") {
80                         return 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") {
86                         return NOCLIP;
87                 } else if (name == "border") {
88                         return BORDER;
89                 } else if (name == "bgimg") {
90                         return BGIMG;
91                 } else if (name == "bgimg_hovered") {
92                         return BGIMG_HOVERED;
93                 } else if (name == "bgimg_middle") {
94                         return BGIMG_MIDDLE;
95                 } else if (name == "bgimg_pressed") {
96                         return BGIMG_PRESSED;
97                 } else if (name == "fgimg") {
98                         return 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") {
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 {
120                         return NONE;
121                 }
122         }
123
124         std::string get(Property prop, std::string def) const
125         {
126                 const auto &val = properties[prop];
127                 return val.empty() ? def : val;
128         }
129
130         void set(Property prop, const std::string &value)
131         {
132                 properties[prop] = value;
133                 property_set[prop] = true;
134         }
135
136         //! Parses a name and returns the corresponding state enum
137         static State getStateByName(const std::string &name)
138         {
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;
145                 } else {
146                         return STATE_INVALID;
147                 }
148         }
149
150         //! Gets the state that this style is intended for
151         State getState() const
152         {
153                 return state_map;
154         }
155
156         //! Set the given state on this style
157         void addState(State state)
158         {
159                 FATAL_ERROR_IF(state >= NUM_STATES, "Out-of-bounds state received");
160
161                 state_map = static_cast<State>(state_map | state);
162         }
163
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)
167         {
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];
173                         }
174                 }
175
176                 return temp;
177         }
178
179         video::SColor getColor(Property prop, video::SColor def) const
180         {
181                 const auto &val = properties[prop];
182                 if (val.empty()) {
183                         return def;
184                 }
185
186                 parseColorString(val, def, false, 0xFF);
187                 return def;
188         }
189
190         video::SColor getColor(Property prop) const
191         {
192                 const auto &val = properties[prop];
193                 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
194
195                 video::SColor color;
196                 parseColorString(val, color, false, 0xFF);
197                 return color;
198         }
199
200         std::array<video::SColor, 4> getColorArray(Property prop,
201                 std::array<video::SColor, 4> def) const
202         {
203                 const auto &val = properties[prop];
204                 if (val.empty())
205                         return def;
206
207                 std::vector<std::string> strs;
208                 if (!parseArray(val, strs))
209                         return def;
210
211                 for (size_t i = 0; i <= 3; i++) {
212                         video::SColor color;
213                         if (parseColorString(strs[i], color, false, 0xff))
214                                 def[i] = color;
215                 }
216
217                 return def;
218         }
219
220         std::array<s32, 4> getIntArray(Property prop, std::array<s32, 4> def) const
221         {
222                 const auto &val = properties[prop];
223                 if (val.empty())
224                         return def;
225
226                 std::vector<std::string> strs;
227                 if (!parseArray(val, strs))
228                         return def;
229
230                 for (size_t i = 0; i <= 3; i++)
231                         def[i] = stoi(strs[i]);
232
233                 return def;
234         }
235
236         irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const
237         {
238                 const auto &val = properties[prop];
239                 if (val.empty())
240                         return def;
241
242                 irr::core::rect<s32> rect;
243                 if (!parseRect(val, &rect))
244                         return def;
245
246                 return rect;
247         }
248
249         irr::core::rect<s32> getRect(Property prop) const
250         {
251                 const auto &val = properties[prop];
252                 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
253
254                 irr::core::rect<s32> rect;
255                 parseRect(val, &rect);
256                 return rect;
257         }
258
259         irr::core::vector2d<s32> getVector2i(Property prop, irr::core::vector2d<s32> def) const
260         {
261                 const auto &val = properties[prop];
262                 if (val.empty())
263                         return def;
264
265                 irr::core::vector2d<s32> vec;
266                 if (!parseVector2i(val, &vec))
267                         return def;
268
269                 return vec;
270         }
271
272         irr::core::vector2d<s32> getVector2i(Property prop) const
273         {
274                 const auto &val = properties[prop];
275                 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
276
277                 irr::core::vector2d<s32> vec;
278                 parseVector2i(val, &vec);
279                 return vec;
280         }
281
282         gui::IGUIFont *getFont() const
283         {
284                 FontSpec spec(FONT_SIZE_UNSPECIFIED, FM_Standard, false, false);
285
286                 const std::string &font = properties[FONT];
287                 const std::string &size = properties[FONT_SIZE];
288
289                 if (font.empty() && size.empty())
290                         return nullptr;
291
292                 std::vector<std::string> modes = split(font, ',');
293
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")
298                                 spec.mode = FM_Mono;
299                         else if (modes[i] == "bold")
300                                 spec.bold = true;
301                         else if (modes[i] == "italic")
302                                 spec.italic = true;
303                 }
304
305                 if (!size.empty()) {
306                         int calc_size = 1;
307
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);
313                         } else {
314                                 calc_size = stoi(size);
315                         }
316
317                         spec.size = (unsigned)std::min(std::max(calc_size, 1), 999);
318                 }
319
320                 return g_fontengine->getFont(spec);
321         }
322
323         video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc,
324                         video::ITexture *def) const
325         {
326                 const auto &val = properties[prop];
327                 if (val.empty()) {
328                         return def;
329                 }
330
331                 video::ITexture *texture = tsrc->getTexture(val);
332
333                 return texture;
334         }
335
336         video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc) const
337         {
338                 const auto &val = properties[prop];
339                 FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
340
341                 video::ITexture *texture = tsrc->getTexture(val);
342
343                 return texture;
344         }
345
346         bool getBool(Property prop, bool def) const
347         {
348                 const auto &val = properties[prop];
349                 if (val.empty()) {
350                         return def;
351                 }
352
353                 return is_yes(val);
354         }
355
356         inline bool isNotDefault(Property prop) const
357         {
358                 return !properties[prop].empty();
359         }
360
361         inline bool hasProperty(Property prop) const { return property_set[prop]; }
362
363         StyleSpec &operator|=(const StyleSpec &other)
364         {
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, ""));
369                         }
370                 }
371
372                 return *this;
373         }
374
375         StyleSpec operator|(const StyleSpec &other) const
376         {
377                 StyleSpec newspec = *this;
378                 newspec |= other;
379                 return newspec;
380         }
381
382 private:
383         bool parseArray(const std::string &value, std::vector<std::string> &arr) const
384         {
385                 std::vector<std::string> strs = split(value, ',');
386
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) {
392                         arr = strs;
393                 } else {
394                         warningstream << "Invalid array size (" << strs.size()
395                                         << " arguments): \"" << value << "\"" << std::endl;
396                         return false;
397                 }
398                 return true;
399         }
400
401         bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const
402         {
403                 irr::core::rect<s32> rect;
404                 std::vector<std::string> v_rect = split(value, ',');
405
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]));
421                 } else {
422                         warningstream << "Invalid rectangle string format: \"" << value
423                                         << "\"" << std::endl;
424                         return false;
425                 }
426
427                 *parsed_rect = rect;
428
429                 return true;
430         }
431
432         bool parseVector2i(const std::string &value, irr::core::vector2d<s32> *parsed_vec) const
433         {
434                 irr::core::vector2d<s32> vec;
435                 std::vector<std::string> v_vector = split(value, ',');
436
437                 if (v_vector.size() == 1) {
438                         s32 x = stoi(v_vector[0]);
439                         vec.X = x;
440                         vec.Y = x;
441                 } else if (v_vector.size() == 2) {
442                         s32 x = stoi(v_vector[0]);
443                         s32 y = stoi(v_vector[1]);
444                         vec.X = x;
445                         vec.Y = y;
446                 } else {
447                         warningstream << "Invalid vector2d string format: \"" << value
448                                         << "\"" << std::endl;
449                         return false;
450                 }
451
452                 *parsed_vec = vec;
453
454                 return true;
455         }
456 };