1 // -*-c++-*-
2 
3 #pragma once
4 
5 #include <vector>
6 #include <string>
7 #include <unordered_map>
8 #include <unordered_set>
9 #include <vector>
10 #include <memory>
11 #include <atomic>
12 #include <iostream>
13 #include <sstream>
14 #include "DebugHelpers.h"
15 #include "globals.h"
16 
17 #if ESCAPE_FROM_VSTGUI
18 #include "efvg/escape_from_vstgui.h"
19 #else
20 #include "vstgui/lib/ccolor.h"
21 #include "vstgui/lib/crect.h"
22 #include "vstgui/lib/cpoint.h"
23 #include "vstgui/lib/cdrawdefs.h"
24 #include "vstgui/lib/cfont.h"
25 #endif
26 
27 #include "SkinModel.h"
28 #include "SkinColors.h"
29 #include "filesystem/import.h"
30 
31 /*
32 ** Support for rudimentary skinning in Surge
33 **
34 ** SkinSupport provides a pair of classes, a SkinManager and a SkinDB
35 ** The SkinManager singleton loads and applys the SkinDB to various places as
36 ** appropriate. The SkinDB has all the information you would need
37 ** to skin yourself, and answers various queries.
38 */
39 
40 class SurgeStorage;
41 class SurgeBitmaps;
42 class CScalableBitmap;
43 class TiXmlElement;
44 
45 #if !ESCAPE_FROM_VSTGUI
46 namespace VSTGUI
47 {
48 class CFrame;
49 }
50 #endif
51 
52 #define FIXMEERROR SkinDB::get().errorStream
53 
54 namespace Surge
55 {
56 
57 template <typename T> class Maybe
58 {
59   public:
60     Maybe() : _empty(true){};
61     explicit Maybe(const T &value) : _empty(false), _value(value){};
62 
63     T fromJust() const
64     {
65         if (isJust())
66         {
67             return _value;
68         }
69         else
70         {
71             throw "Cannot get value from Nothing";
72         }
73     }
74 
75     bool isJust() const { return !_empty; }
76     bool isNothing() const { return _empty; }
77 
78     static bool isJust(const Maybe &m) { return m.isJust(); }
79     static bool isNothing(const Maybe &m) { return m.isNothing(); }
80 
81   private:
82     bool _empty;
83     T _value;
84 };
85 
86 namespace UI
87 {
88 
89 /*
90  * This function is defined in SkinFontLoader.cpp
91  */
92 void addFontSearchPathToSystem(const fs::path &p);
93 
94 extern const std::string NoneClassName;
95 class SkinDB;
96 
97 class Skin
98 {
99   public:
100     /*
101      * Skin Versions
102      * 1. The 1.7 and 1.8 skin engine
103      * 2. The 1.9 skin engine (adds notch, image labels, more)
104      */
105     static constexpr int current_format_version = 2;
106     int version;
107     inline int getVersion() const { return version; }
108 
109     typedef std::shared_ptr<Skin> ptr_t;
110     typedef std::unordered_map<std::string, std::string> props_t;
111 
112     friend class SkinDB;
113 
114     Skin(const std::string &root, const std::string &name);
115     ~Skin();
116 
117     bool reloadSkin(std::shared_ptr<SurgeBitmaps> bitmapStore);
118 
119     std::string resourceName(const std::string &relativeName)
120     {
121 #if WINDOWS
122         return root + name + "\\" + relativeName;
123 #else
124         return root + name + "/" + relativeName;
125 #endif
126     }
127 
128     static bool setAllCapsProperty(std::string propertyValue);
129     static VSTGUI::CTxtFace setFontStyleProperty(std::string propertyValue);
130     static VSTGUI::CHoriTxtAlign setTextAlignProperty(std::string propertyValue);
131 
132     std::string root;
133     std::string name;
134     std::string category;
135 
136     std::string displayName;
137     std::string author;
138     std::string authorURL;
139 
140     struct ComponentClass
141     {
142         typedef std::shared_ptr<ComponentClass> ptr_t;
143         std::string name;
144         props_t allprops;
145     };
146 
147     struct Control
148     {
149         typedef std::shared_ptr<Control> ptr_t;
150         int x, y, w, h;
151 
152         Surge::Skin::Component defaultComponent;
153 
154         std::string ui_id;
155         typedef enum
156         {
157             UIID,
158             LABEL,
159         } Type;
160         Type type;
161         std::string classname;
162         std::string ultimateparentclassname;
163         props_t allprops;
164 
165         bool parentResolved = false;
166 
167         std::string toString() const
168         {
169             std::ostringstream oss;
170 
171             switch (type)
172             {
173             case UIID:
174                 oss << "UIID:" << ui_id;
175                 break;
176             case LABEL:
177                 oss << "LABEL";
178                 break;
179             }
180             oss << " (x=" << x << " y=" << y << " w=" << w << " h=" << h << ")";
181             return oss.str();
182         }
183 
184         VSTGUI::CRect getRect() const
185         {
186             return VSTGUI::CRect(VSTGUI::CPoint(x, y), VSTGUI::CPoint(w, h));
187         }
188         void copyFromConnector(const Surge::Skin::Connector &c, int skinVersion);
189     };
190 
191     struct ControlGroup
192     {
193         typedef std::shared_ptr<ControlGroup> ptr_t;
194         std::vector<ControlGroup::ptr_t> childGroups;
195         std::vector<Control::ptr_t> childControls;
196 
197         int x = 0, y = 0, w = -1, h = -1;
198 
199         bool userGroup = false;
200 
201         props_t allprops;
202     };
203 
204     bool hasColor(const std::string &id) const;
205     VSTGUI::CColor
206     getColor(const std::string &id, const VSTGUI::CColor &def,
207              std::unordered_set<std::string> noLoops = std::unordered_set<std::string>()) const;
208     VSTGUI::CColor
209     getColor(const std::string &id, const Surge::Skin::Color &def,
210              std::unordered_set<std::string> noLoops = std::unordered_set<std::string>()) const
211     {
212         return getColor(id, VSTGUI::CColor(def.r, def.g, def.b, def.a), noLoops);
213     }
214 
215     VSTGUI::CColor getColor(const std::string &id)
216     {
217         return getColor(id, Surge::Skin::Color::colorByName(id));
218     }
219 
220     VSTGUI::CColor colorFromHexString(const std::string &hex) const;
221 
222   private:
223     VSTGUI::CColor
224     getColor(const Surge::Skin::Color &id, const VSTGUI::CColor &def,
225              std::unordered_set<std::string> noLoops = std::unordered_set<std::string>()) const
226     {
227         return getColor(id.name, def, noLoops);
228     }
229 
230   public:
231     VSTGUI::CColor
232     getColor(const Surge::Skin::Color &id,
233              std::unordered_set<std::string> noLoops = std::unordered_set<std::string>()) const
234     {
235         return getColor(id, VSTGUI::CColor(id.r, id.g, id.b, id.a), noLoops);
236     }
237 
238     bool hasColor(const Surge::Skin::Color &col) const { return hasColor(col.name); }
239 
240     Skin::Control::ptr_t controlForUIID(const std::string &ui_id) const
241     {
242         // FIXME don't be stupid like this of course
243         for (auto ic : controls)
244         {
245             if (ic->type == Control::Type::UIID && ic->ui_id == ui_id)
246             {
247                 return ic;
248             }
249         }
250 
251         return nullptr;
252     }
253 
254     Skin::Control::ptr_t getOrCreateControlForConnector(const std::string &s)
255     {
256         return getOrCreateControlForConnector(Surge::Skin::Connector::connectorByID(s));
257     }
258     Skin::Control::ptr_t getOrCreateControlForConnector(const Surge::Skin::Connector &c)
259     {
260         auto res = controlForUIID(c.payload->id);
261         if (!res)
262         {
263             res = std::make_shared<Surge::UI::Skin::Control>();
264             res->copyFromConnector(c, getVersion());
265             // resolveBaseParentOffsets( res );
266             controls.push_back(res);
267         }
268         return res;
269     }
270 
271     void resolveBaseParentOffsets(Skin::Control::ptr_t);
272 
273     void addControl(Skin::Control::ptr_t c) { controls.push_back(c); }
274 
275     Maybe<std::string> propertyValue(Skin::Control::ptr_t c,
276                                      Surge::Skin::Component::Properties pkey)
277     {
278         if (!c->defaultComponent.hasProperty(pkey))
279             return Maybe<std::string>();
280 
281         auto stringNames = c->defaultComponent.payload->propertyNamesMap[pkey];
282 
283         /*
284         ** Traverse class heirarchy looking for value
285         */
286 
287         for (auto const &key : stringNames)
288             if (c->allprops.find(key) != c->allprops.end())
289                 return Maybe<std::string>(c->allprops[key]);
290 
291         auto cl = componentClasses[c->classname];
292         if (!cl)
293             return Maybe<std::string>();
294 
295         do
296         {
297             for (auto const &key : stringNames)
298                 if (cl->allprops.find(key) != cl->allprops.end())
299                     return Maybe<std::string>(cl->allprops[key]);
300 
301             if (cl->allprops.find("parent") != cl->allprops.end() &&
302                 componentClasses.find(cl->allprops["parent"]) != componentClasses.end())
303             {
304                 cl = componentClasses[cl->allprops["parent"]];
305             }
306             else
307                 return Maybe<std::string>();
308         } while (cl);
309 
310         return Maybe<std::string>();
311     }
312 
313     std::string propertyValue(Skin::Control::ptr_t c, Surge::Skin::Component::Properties key,
314                               const std::string &defaultValue)
315     {
316         auto pv = propertyValue(c, key);
317         if (pv.isJust())
318             return pv.fromJust();
319         else
320             return defaultValue;
321     }
322 
323     std::string customBackgroundImage() const { return bgimg; }
324 
325     int getWindowSizeX() const { return szx; }
326     int getWindowSizeY() const { return szy; }
327 
328     bool hasFixedZooms() const { return zooms.size() != 0; }
329     std::vector<int> getFixedZooms() const { return zooms; }
330     CScalableBitmap *backgroundBitmapForControl(Skin::Control::ptr_t c,
331                                                 std::shared_ptr<SurgeBitmaps> bitmapStore);
332 
333     typedef enum
334     {
335         HOVER,
336         HOVER_OVER_ON,
337     } HoverType;
338 
339     CScalableBitmap *
340     hoverBitmapOverlayForBackgroundBitmap(Skin::Control::ptr_t c, CScalableBitmap *b,
341                                           std::shared_ptr<SurgeBitmaps> bitmapStore, HoverType t);
342 
343     std::vector<Skin::Control::ptr_t> getLabels() const
344     {
345         std::vector<Skin::Control::ptr_t> labels;
346         for (auto ic : controls)
347         {
348             if (ic->type == Control::LABEL)
349             {
350                 labels.push_back(ic);
351             }
352         }
353         return labels;
354     }
355 
356     static const std::string defaultImageIDPrefix;
357 
358   private:
359     static std::atomic<int> instances;
360     struct GlobalPayload
361     {
362         GlobalPayload(const props_t &p) : props(p) {}
363         props_t props;
364         std::vector<std::pair<std::string, props_t>> children;
365     };
366     std::vector<std::pair<std::string, GlobalPayload>> globals;
367     std::string bgimg = "";
368     int szx = BASE_WINDOW_SIZE_X, szy = BASE_WINDOW_SIZE_Y;
369 
370     struct ColorStore
371     {
372         VSTGUI::CColor color;
373         std::string alias;
374 
375         typedef enum
376         {
377             COLOR,
378             ALIAS,
379             UNRESOLVED_ALIAS
380         } Type;
381 
382         Type type;
383         ColorStore() : type(COLOR), color(VSTGUI::kBlackCColor) {}
384         ColorStore(VSTGUI::CColor c) : type(COLOR), color(c) {}
385         ColorStore(std::string a) : type(ALIAS), alias(a) {}
386         ColorStore(std::string a, Type t) : type(t), alias(a) {}
387     };
388     std::unordered_map<std::string, ColorStore> colors;
389     std::unordered_map<std::string, int> imageStringToId;
390     std::unordered_set<int> imageAllowedIds;
391     ControlGroup::ptr_t rootControl;
392     std::vector<Control::ptr_t> controls;
393     std::unordered_map<std::string, ComponentClass::ptr_t> componentClasses;
394     std::vector<int> zooms;
395     bool recursiveGroupParse(ControlGroup::ptr_t parent, TiXmlElement *groupList,
396                              bool topLevel = true);
397 };
398 
399 class SkinDB
400 {
401   public:
402     static SkinDB &get();
403 
404     struct Entry
405     {
406 
407         enum RootType
408         {
409             UNKNOWN,
410             FACTORY,
411             USER
412         } rootType = UNKNOWN;
413 
414         std::string root;
415         std::string name;
416         std::string displayName;
417         std::string category;
418         bool parseable;
419 
420         // Since we want to use this as a map
421         bool operator==(const Entry &o) const { return root == o.root && name == o.name; }
422 
423         struct hash
424         {
425             size_t operator()(const Entry &e) const
426             {
427                 std::hash<std::string> h;
428                 return h(e.root) ^ h(e.name);
429             }
430         };
431 
432         bool matchesSkin(const Skin::ptr_t s) const
433         {
434             return s.get() && s->root == root && s->name == name;
435         }
436     };
437 
438     void rescanForSkins(SurgeStorage *);
439     const std::vector<Entry> &getAvailableSkins() const { return availableSkins; }
440     Maybe<Entry> getEntryByRootAndName(const std::string &r, const std::string &n)
441     {
442         for (auto &a : availableSkins)
443             if (a.root == r && a.name == n)
444                 return Maybe<Entry>(a);
445         return Maybe<Entry>();
446     }
447     const Entry &getDefaultSkinEntry() const { return defaultSkinEntry; }
448 
449     Skin::ptr_t getSkin(const Entry &skinEntry);
450     Skin::ptr_t defaultSkin(SurgeStorage *);
451 
452     std::string getErrorString() { return errorStream.str(); };
453     std::string getAndResetErrorString()
454     {
455         auto s = errorStream.str();
456         errorStream = std::ostringstream();
457         return s;
458     }
459 
460   private:
461     SkinDB();
462     ~SkinDB();
463     SkinDB(const SkinDB &) = delete;
464     SkinDB &operator=(const SkinDB &) = delete;
465 
466     std::vector<Entry> availableSkins;
467     std::unordered_map<Entry, std::shared_ptr<Skin>, Entry::hash> skins;
468     Entry defaultSkinEntry;
469     bool foundDefaultSkinEntry = false;
470 
471     static std::shared_ptr<SkinDB> instance;
472     static std::ostringstream errorStream;
473 
474     friend class Skin;
475 };
476 
477 class SkinConsumingComponent
478 {
479   public:
480     virtual ~SkinConsumingComponent() {}
481     virtual void setSkin(Skin::ptr_t s) { setSkin(s, nullptr, nullptr); }
482     virtual void setSkin(Skin::ptr_t s, std::shared_ptr<SurgeBitmaps> b) { setSkin(s, b, nullptr); }
483     virtual void setSkin(Skin::ptr_t s, std::shared_ptr<SurgeBitmaps> b, Skin::Control::ptr_t c)
484     {
485         skin = s;
486         associatedBitmapStore = b;
487         skinControl = c;
488         onSkinChanged();
489     }
490 
491     virtual void onSkinChanged() {}
492 
493   protected:
494     Skin::ptr_t skin = nullptr;
495     Skin::Control::ptr_t skinControl = nullptr;
496     std::shared_ptr<SurgeBitmaps> associatedBitmapStore = nullptr;
497 };
498 } // namespace UI
499 } // namespace Surge
500