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