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