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