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